Save This Page
Home » apache-cxf-2.2.7-src » org.apache » cxf » transport » http » [javadoc | source]
    1   /**
    2    * Licensed to the Apache Software Foundation (ASF) under one
    3    * or more contributor license agreements. See the NOTICE file
    4    * distributed with this work for additional information
    5    * regarding copyright ownership. The ASF licenses this file
    6    * to you under the Apache License, Version 2.0 (the
    7    * "License"); you may not use this file except in compliance
    8    * with the License. You may obtain a copy of the License at
    9    *
   10    * http://www.apache.org/licenses/LICENSE-2.0
   11    *
   12    * Unless required by applicable law or agreed to in writing,
   13    * software distributed under the License is distributed on an
   14    * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
   15    * KIND, either express or implied. See the License for the
   16    * specific language governing permissions and limitations
   17    * under the License.
   18    */
   19   package org.apache.cxf.transport.http;
   20   
   21   
   22   import java.io.IOException;
   23   import java.io.InputStream;
   24   import java.io.OutputStream;
   25   import java.io.PushbackInputStream;
   26   import java.net.HttpRetryException;
   27   import java.net.HttpURLConnection;
   28   import java.net.InetSocketAddress;
   29   import java.net.MalformedURLException;
   30   import java.net.Proxy;
   31   import java.net.URL;
   32   import java.net.URLConnection;
   33   import java.util.ArrayList;
   34   import java.util.Arrays;
   35   import java.util.HashMap;
   36   import java.util.HashSet;
   37   import java.util.LinkedHashMap;
   38   import java.util.List;
   39   import java.util.Map;
   40   import java.util.Set;
   41   import java.util.concurrent.ConcurrentHashMap;
   42   import java.util.logging.Level;
   43   import java.util.logging.Logger;
   44   
   45   import javax.xml.namespace.QName;
   46   
   47   import org.apache.cxf.Bus;
   48   import org.apache.cxf.common.logging.LogUtils;
   49   import org.apache.cxf.common.util.Base64Utility;
   50   import org.apache.cxf.common.util.StringUtils;
   51   import org.apache.cxf.configuration.Configurable;
   52   import org.apache.cxf.configuration.jsse.TLSClientParameters;
   53   import org.apache.cxf.configuration.security.AuthorizationPolicy;
   54   import org.apache.cxf.configuration.security.CertificateConstraintsType;
   55   import org.apache.cxf.configuration.security.ProxyAuthorizationPolicy;
   56   import org.apache.cxf.helpers.CastUtils;
   57   import org.apache.cxf.helpers.HttpHeaderHelper;
   58   import org.apache.cxf.helpers.IOUtils;
   59   import org.apache.cxf.helpers.LoadingByteArrayOutputStream;
   60   import org.apache.cxf.io.AbstractThresholdOutputStream;
   61   import org.apache.cxf.io.CacheAndWriteOutputStream;
   62   import org.apache.cxf.io.CachedOutputStream;
   63   import org.apache.cxf.message.Exchange;
   64   import org.apache.cxf.message.ExchangeImpl;
   65   import org.apache.cxf.message.Message;
   66   import org.apache.cxf.message.MessageImpl;
   67   import org.apache.cxf.message.MessageUtils;
   68   import org.apache.cxf.phase.PhaseInterceptorChain;
   69   import org.apache.cxf.service.model.EndpointInfo;
   70   import org.apache.cxf.transport.AbstractConduit;
   71   import org.apache.cxf.transport.Destination;
   72   import org.apache.cxf.transport.DestinationFactory;
   73   import org.apache.cxf.transport.DestinationFactoryManager;
   74   import org.apache.cxf.transport.MessageObserver;
   75   import org.apache.cxf.transport.http.policy.PolicyUtils;
   76   import org.apache.cxf.transport.https.CertConstraints;
   77   import org.apache.cxf.transport.https.CertConstraintsInterceptor;
   78   import org.apache.cxf.transport.https.CertConstraintsJaxBUtils;
   79   import org.apache.cxf.transports.http.configuration.HTTPClientPolicy;
   80   import org.apache.cxf.version.Version;
   81   import org.apache.cxf.workqueue.AutomaticWorkQueue;
   82   import org.apache.cxf.workqueue.WorkQueueManager;
   83   import org.apache.cxf.ws.addressing.EndpointReferenceType;
   84   import org.apache.cxf.ws.policy.Assertor;
   85   import org.apache.cxf.ws.policy.PolicyEngine;
   86   import org.apache.cxf.wsdl.EndpointReferenceUtils;
   87   
   88   import static org.apache.cxf.message.Message.DECOUPLED_CHANNEL_MESSAGE;
   89   
   90   
   91   /*
   92    * HTTP Conduit implementation.
   93    * <p>
   94    * This implementation is a based on the java.net.URLConnection interface and
   95    * dependent upon installed implementations of that URLConnection, 
   96    * HttpURLConnection, and HttpsURLConnection. Currently, this implementation
   97    * has been known to work with the Sun JDK 1.5 default implementations. The
   98    * HttpsURLConnection is part of Sun's implementation of the JSSE. 
   99    * Presently, the source code for the Sun JSSE implementation is unavailable
  100    * and therefore we may only lay a guess of whether its HttpsURLConnection
  101    * implementation correctly works as far as security is concerned.
  102    * <p>
  103    * The Trust Decision. If a MessageTrustDecider is configured/set for the 
  104    * Conduit, it is called upon the first flush of the headers in the 
  105    * WrappedOutputStream. This reason for this approach is two-fold. 
  106    * Theoretically, in order to get connection information out of the 
  107    * URLConnection, it must be "connected". We assume that its implementation will
  108    * only follow through up to the point at which it will be ready to send
  109    * one byte of data down to the endpoint, but through proxies, and the 
  110    * commpletion of a TLS handshake in the case of HttpsURLConnection. 
  111    * However, if we force the connect() call right away, the default
  112    * implementations will not allow any calls to add/setRequestProperty,
  113    * throwing an exception that the URLConnection is already connected. 
  114    * <p>
  115    * We need to keep the semantic that later CXF interceptors may add to the 
  116    * PROTOCOL_HEADERS in the Message. This architectual decision forces us to 
  117    * delay the connection until after that point, then pulling the trust decision.
  118    * <p>
  119    * The security caveat is that we don't really know when the connection is 
  120    * really established. The call to "connect" is stated to force the 
  121    * "connection," but it is a no-op if the connection was already established. 
  122    * It is entirely possible that an implementation of an URLConnection may 
  123    * indeed connect at will and start sending the headers down the connection 
  124    * during calls to add/setRequestProperty!
  125    * <p>
  126    * We know that the JDK 1.5 sun.com.net.www.HttpURLConnection does not send
  127    * this information before the "connect" call, because we can look at the
  128    * source code. However, we can only assume, not verify, that the JSSE 1.5 
  129    * HttpsURLConnection does the same, in that it is probable that the 
  130    * HttpsURLConnection shares the HttpURLConnection implementation.
  131    * <p>
  132    * Due to these implementations following redirects without trust checks, we
  133    * force the URLConnection implementations not to follow redirects. If 
  134    * client side policy dictates that we follow redirects, trust decisions are
  135    * placed before each retransmit. On a redirect, any authorization information
  136    * dynamically acquired by a BasicAuth UserPass supplier is removed before
  137    * being retransmitted, as it may no longer be applicable to the new url to
  138    * which the connection is redirected.
  139    */
  140   
  141   /**
  142    * This Conduit handles the "http" and "https" transport protocols. An
  143    * instance is governed by policies either explicitly set or by 
  144    * configuration.
  145    */
  146   public class HTTPConduit 
  147       extends AbstractConduit 
  148       implements Configurable, Assertor {  
  149   
  150       /**
  151        *  This constant is the Message(Map) key for the HttpURLConnection that
  152        *  is used to get the response.
  153        */
  154       public static final String KEY_HTTP_CONNECTION = "http.connection";
  155   
  156       /**
  157        * This constant is the Message(Map) key for a list of visited URLs that
  158        * is used in redirect loop protection.
  159        */
  160       private static final String KEY_VISITED_URLS = "VisitedURLs";
  161   
  162       /**
  163        * This constant is the Message(Map) key for a list of URLs that
  164        * is used in authorization loop protection.
  165        */
  166       private static final String KEY_AUTH_URLS = "AuthURLs";
  167       
  168       /**
  169        * The Logger for this class.
  170        */
  171       private static final Logger LOG = LogUtils.getL7dLogger(HTTPConduit.class);
  172       
  173       /**
  174        * This constant holds the suffix ".http-conduit" that is appended to the 
  175        * Endpoint Qname to give the configuration name of this conduit.
  176        */
  177       private static final String SC_HTTP_CONDUIT_SUFFIX = ".http-conduit";
  178       
  179       /**
  180        * This field holds the connection factory, which primarily is used to 
  181        * factor out SSL specific code from this implementation.
  182        * <p>
  183        * This field is "protected" to facilitate some contrived UnitTesting so
  184        * that an extended class may alter its value with an EasyMock URLConnection
  185        * Factory. 
  186        */
  187       protected HttpURLConnectionFactory connectionFactory;
  188       
  189       /**
  190        *  This field holds a reference to the CXF bus associated this conduit.
  191        */
  192       private final Bus bus;
  193   
  194       /**
  195        * This field is used for two reasons. First it provides the base name for
  196        * the conduit for Spring configuration. The other is to hold default 
  197        * address information, should it not be supplied in the Message Map, by the 
  198        * Message.ENDPOINT_ADDRESS property.
  199        */
  200       private final EndpointInfo endpointInfo;
  201       
  202   
  203       /**
  204        * This field holds the "default" URL for this particular conduit, which
  205        * is created on demand.
  206        */
  207       private URL defaultEndpointURL;
  208       private String defaultEndpointURLString;
  209       private boolean fromEndpointReferenceType;
  210   
  211       private Destination decoupledDestination;
  212       private MessageObserver decoupledObserver;
  213       private int decoupledDestinationRefCount;
  214       
  215       // Configurable values
  216       
  217       /**
  218        * This field holds the QoS configuration settings for this conduit.
  219        * This field is injected via spring configuration based on the conduit 
  220        * name.
  221        */
  222       private HTTPClientPolicy clientSidePolicy;
  223       
  224       /**
  225        * This field holds the password authorization configuration.
  226        * This field is injected via spring configuration based on the conduit 
  227        * name.
  228       */
  229       private AuthorizationPolicy authorizationPolicy;
  230       
  231       /**
  232        * This field holds the password authorization configuration for the 
  233        * configured proxy. This field is injected via spring configuration based 
  234        * on the conduit name.
  235        */
  236       private ProxyAuthorizationPolicy proxyAuthorizationPolicy;
  237   
  238       /**
  239        * This field holds the configuration TLS configuration which
  240        * is programmatically configured. 
  241        */
  242       private TLSClientParameters tlsClientParameters;
  243       
  244       /**
  245        * This field contains the MessageTrustDecider.
  246        */
  247       private MessageTrustDecider trustDecider;
  248       
  249       /**
  250        * This field contains the HttpAuthSupplier.
  251        */
  252       private HttpAuthSupplier authSupplier;
  253   
  254       /**
  255        * This boolean signfies that that finalizeConfig is called, which is
  256        * after the HTTPTransportFactory configures this object via spring.
  257        * At this point, any change by a "setter" is dynamic, and any change
  258        * should be handled as such.
  259        */
  260       private boolean configFinalized;
  261   
  262       /**
  263        * Variables for holding session state if sessions are supposed to be maintained
  264        */
  265       private Map<String, Cookie> sessionCookies = new ConcurrentHashMap<String, Cookie>();
  266       private boolean maintainSession;
  267       
  268       private CertConstraints certConstraints;
  269   
  270       /**
  271        * Constructor
  272        * 
  273        * @param b the associated Bus
  274        * @param ei the endpoint info of the initiator
  275        * @throws IOException
  276        */
  277       public HTTPConduit(Bus b, EndpointInfo ei) throws IOException {
  278           this(b,
  279                ei,
  280                null);
  281       }
  282   
  283       /**
  284        * Constructor
  285        * 
  286        * @param b the associated Bus.
  287        * @param endpoint the endpoint info of the initiator.
  288        * @param t the endpoint reference of the target.
  289        * @throws IOException
  290        */
  291       public HTTPConduit(Bus b, 
  292                          EndpointInfo ei, 
  293                          EndpointReferenceType t) throws IOException {
  294           super(getTargetReference(ei, t, b));
  295           
  296           bus = b;
  297           endpointInfo = ei;
  298   
  299           if (t != null) {
  300               fromEndpointReferenceType = true;
  301           }
  302   
  303           initializeConfig();
  304           CXFAuthenticator.addAuthenticator();
  305       }
  306   
  307       /**
  308        * This method returns the registered Logger for this conduit.
  309        */
  310       protected Logger getLogger() {
  311           return LOG;
  312       }
  313   
  314       /**
  315        * This method returns the name of the conduit, which is based on the
  316        * endpoint name plus the SC_HTTP_CONDUIT_SUFFIX.
  317        * @return
  318        */
  319       public final String getConduitName() {
  320           return endpointInfo.getName() + SC_HTTP_CONDUIT_SUFFIX;
  321       }
  322   
  323       /**
  324        * This method is called from the constructor which initializes
  325        * the configuration. The TransportFactory will call configureBean
  326        * on this object after construction.
  327        */    
  328       private void initializeConfig() {
  329       
  330           // wsdl extensors are superseded by policies which in        
  331           // turn are superseded by injection                          
  332   
  333           PolicyEngine pe = bus.getExtension(PolicyEngine.class);      
  334           if (null != pe && pe.isEnabled() && endpointInfo.getService() != null) {                          
  335               clientSidePolicy =                                       
  336                   PolicyUtils.getClient(pe, endpointInfo, this);              
  337           }                                                            
  338   
  339       }
  340       
  341       /**
  342        * This call gets called by the HTTPTransportFactory after it
  343        * causes an injection of the Spring configuration properties
  344        * of this Conduit.
  345        */
  346       protected void finalizeConfig() {
  347           // See if not set by configuration, if there are defaults
  348           // in order from the Endpoint, Service, or Bus.
  349           
  350           if (this.clientSidePolicy == null) {
  351               clientSidePolicy = endpointInfo.getTraversedExtensor(
  352                       new HTTPClientPolicy(), HTTPClientPolicy.class);
  353           }
  354           if (this.authorizationPolicy == null) {
  355               authorizationPolicy = endpointInfo.getTraversedExtensor(
  356                       new AuthorizationPolicy(), AuthorizationPolicy.class);
  357              
  358           }
  359           if (this.proxyAuthorizationPolicy == null) {
  360               proxyAuthorizationPolicy = endpointInfo.getTraversedExtensor(
  361                       new ProxyAuthorizationPolicy(), ProxyAuthorizationPolicy.class);
  362              
  363           }
  364           if (this.tlsClientParameters == null) {
  365               tlsClientParameters = endpointInfo.getTraversedExtensor(
  366                       null, TLSClientParameters.class);
  367           }
  368           if (this.trustDecider == null) {
  369               trustDecider = endpointInfo.getTraversedExtensor(
  370                       null, MessageTrustDecider.class);
  371           }
  372           if (this.authSupplier == null) {
  373               authSupplier = endpointInfo.getTraversedExtensor(
  374                       null, HttpAuthSupplier.class);
  375           }
  376           if (trustDecider == null) {
  377               if (LOG.isLoggable(Level.FINE)) {
  378                   LOG.log(Level.FINE,
  379                       "No Trust Decider configured for Conduit '"
  380                       + getConduitName() + "'");
  381               }
  382           } else {
  383               if (LOG.isLoggable(Level.FINE)) {
  384                   LOG.log(Level.FINE, "Message Trust Decider of class '" 
  385                       + trustDecider.getClass().getName()
  386                       + "' with logical name of '"
  387                       + trustDecider.getLogicalName()
  388                       + "' has been configured for Conduit '" 
  389                       + getConduitName()
  390                       + "'");
  391               }
  392           }
  393           if (authSupplier == null) {
  394               if (LOG.isLoggable(Level.FINE)) {
  395                   LOG.log(Level.FINE,
  396                       "No Auth Supplier configured for Conduit '"
  397                       + getConduitName() + "'");
  398               }
  399           } else {
  400               if (LOG.isLoggable(Level.FINE)) {
  401                   LOG.log(Level.FINE, "HttpAuthSupplier of class '" 
  402                       + authSupplier.getClass().getName()
  403                       + "' with logical name of '"
  404                       + authSupplier.getLogicalName()
  405                       + "' has been configured for Conduit '" 
  406                       + getConduitName()
  407                       + "'");
  408               }
  409           }
  410           if (this.tlsClientParameters != null) {
  411               if (LOG.isLoggable(Level.FINE)) {
  412                   LOG.log(Level.FINE, "Conduit '" + getConduitName()
  413                       + "' has been configured for TLS "
  414                       + "keyManagers " + Arrays.toString(tlsClientParameters.getKeyManagers())
  415                       + "trustManagers " + Arrays.toString(tlsClientParameters.getTrustManagers())
  416                       + "secureRandom " + tlsClientParameters.getSecureRandom()
  417                       + "Disable Common Name (CN) Check: " + tlsClientParameters.isDisableCNCheck());
  418               }
  419           } else {
  420               if (LOG.isLoggable(Level.FINE)) {
  421                   LOG.log(Level.FINE, "Conduit '" + getConduitName()
  422                       + "' has been configured for plain http.");
  423               }
  424           }
  425   
  426           // Get the correct URLConnection factory based on the 
  427           // configuration.
  428           retrieveConnectionFactory(getAddress());
  429   
  430           // We have finalized the configuration. Any configurable entity
  431           // set now, must make changes dynamically.
  432           configFinalized = true;
  433       }
  434       
  435       /**
  436        * Allow access to the cookies that the conduit is maintaining
  437        * @return the sessionCookies map
  438        */
  439       public Map<String, Cookie> getCookies() {
  440           return sessionCookies;
  441       }
  442       
  443       /**
  444        * This method sets the connectionFactory field for this object. It is called
  445        * after an SSL Client Policy is set or an HttpsHostnameVerifier
  446        * because we need to reinitialize the connection factory.
  447        * <p>
  448        * This method is "protected" so that this class may be extended and override
  449        * this method to put an EasyMock URL Connection factory for some contrived 
  450        * UnitTest that will of course break, should the calls to the URL Connection
  451        * Factory get altered.
  452        */
  453       protected synchronized void retrieveConnectionFactory() {
  454           connectionFactory = AbstractHTTPTransportFactory.getConnectionFactory(this, getAddress());
  455       }
  456       protected synchronized void retrieveConnectionFactory(String url) {
  457           connectionFactory = AbstractHTTPTransportFactory.getConnectionFactory(this, url);
  458       }
  459       
  460       
  461       protected synchronized HttpURLConnectionFactory getConnectionFactory(URL url) {
  462           if (connectionFactory == null 
  463               || !url.getProtocol().equals(connectionFactory.getProtocol())) {
  464               retrieveConnectionFactory(url.toString());
  465           }
  466   
  467           return connectionFactory;
  468       }
  469       /**
  470        * Prepare to send an outbound HTTP message over this http conduit to a 
  471        * particular endpoint.
  472        * <P>
  473        * If the Message.PATH_INFO property is set it gets appended
  474        * to the Conduit's endpoint URL. If the Message.QUERY_STRING
  475        * property is set, it gets appended to the resultant URL following
  476        * a "?".
  477        * <P>
  478        * If the Message.HTTP_REQUEST_METHOD property is NOT set, the
  479        * Http request method defaults to "POST".
  480        * <P>
  481        * If the Message.PROTOCOL_HEADERS is not set on the message, it is
  482        * initialized to an empty map.
  483        * <P>
  484        * This call creates the OutputStream for the content of the message.
  485        * It also assigns the created Http(s)URLConnection to the Message
  486        * Map.
  487        * 
  488        * @param message The message to be sent.
  489        */
  490       public void prepare(Message message) throws IOException {
  491           Map<String, List<String>> headers = getSetProtocolHeaders(message);
  492   
  493           // This call can possibly change the conduit endpoint address and 
  494           // protocol from the default set in EndpointInfo that is associated
  495           // with the Conduit.
  496           URL currentURL = setupURL(message);       
  497   
  498           // The need to cache the request is off by default
  499           boolean needToCacheRequest = false;
  500           
  501           HTTPClientPolicy csPolicy = getClient(message);
  502   
  503           HttpURLConnectionFactory f = getConnectionFactory(currentURL);
  504           HttpURLConnection connection = f.createConnection(getProxy(csPolicy), currentURL);
  505           connection.setDoOutput(true);  
  506                
  507           long timeout = csPolicy.getConnectionTimeout();
  508           if (timeout > Integer.MAX_VALUE) {
  509               timeout = Integer.MAX_VALUE;
  510           }
  511           connection.setConnectTimeout((int)timeout);
  512           timeout = csPolicy.getReceiveTimeout();
  513           if (timeout > Integer.MAX_VALUE) {
  514               timeout = Integer.MAX_VALUE;
  515           }
  516           connection.setReadTimeout((int)timeout);
  517           connection.setUseCaches(false);
  518           // We implement redirects in this conduit. We do not
  519           // rely on the underlying URLConnection implementation
  520           // because of trust issues.
  521           connection.setInstanceFollowRedirects(false);
  522           
  523           // If the HTTP_REQUEST_METHOD is not set, the default is "POST".
  524           String httpRequestMethod = 
  525               (String)message.get(Message.HTTP_REQUEST_METHOD);        
  526   
  527           if (null != httpRequestMethod) {
  528               connection.setRequestMethod(httpRequestMethod);
  529           } else {
  530               connection.setRequestMethod("POST");
  531           }
  532                   
  533           boolean isChunking = false;
  534           int chunkThreshold = 0;
  535           // We must cache the request if we have basic auth supplier
  536           // without preemptive basic auth.
  537           if (authSupplier != null) {
  538               String auth = authSupplier.getPreemptiveAuthorization(
  539                       this, currentURL, message);
  540               if (auth == null || authSupplier.requiresRequestCaching()) {
  541                   needToCacheRequest = true;
  542                   isChunking = false;
  543                   LOG.log(Level.FINE,
  544                           "Auth Supplier, but no Premeptive User Pass or Digest auth (nonce may be stale)"
  545                           + " We must cache request.");
  546               }
  547               message.put("AUTH_VALUE", auth);
  548           }
  549           if (csPolicy.isAutoRedirect()) {
  550               needToCacheRequest = true;
  551               LOG.log(Level.FINE, "AutoRedirect is turned on.");
  552           }
  553           if (csPolicy.getMaxRetransmits() > 0) {
  554               needToCacheRequest = true;
  555               LOG.log(Level.FINE, "MaxRetransmits is set > 0.");
  556           }
  557           // DELETE does not work and empty PUTs cause misleading exceptions
  558           // if chunking is enabled
  559           // TODO : ensure chunking can be enabled for non-empty PUTs - if requested
  560           if (connection.getRequestMethod().equals("POST")
  561               && csPolicy.isAllowChunking()) {
  562               //TODO: The chunking mode be configured or at least some
  563               // documented client constant.
  564               //use -1 and allow the URL connection to pick a default value
  565               isChunking = true;
  566               chunkThreshold = csPolicy.getChunkingThreshold();
  567               if (chunkThreshold <= 0) {
  568                   chunkThreshold = 0;
  569                   connection.setChunkedStreamingMode(-1);                    
  570               }
  571           }
  572   
  573           //Do we need to maintain a session?
  574           maintainSession = Boolean.TRUE.equals((Boolean)message.get(Message.MAINTAIN_SESSION));
  575           
  576           //If we have any cookies and we are maintaining sessions, then use them        
  577           if (maintainSession && sessionCookies.size() > 0) {
  578               List<String> cookies = null;
  579               for (String s : headers.keySet()) {
  580                   if (HttpHeaderHelper.COOKIE.equalsIgnoreCase(s)) {
  581                       cookies = headers.remove(s);
  582                       break;
  583                   }
  584               }
  585               if (cookies == null) {
  586                   cookies = new ArrayList<String>();
  587               } else {
  588                   cookies = new ArrayList<String>(cookies);
  589               }
  590               headers.put(HttpHeaderHelper.COOKIE, cookies);
  591               for (Cookie c : sessionCookies.values()) {
  592                   cookies.add(c.requestCookieHeader());
  593               }
  594           }
  595   
  596           // The trust decision is relegated to after the "flushing" of the
  597           // request headers.
  598           
  599           // We place the connection on the message to pick it up
  600           // in the WrappedOutputStream.
  601           
  602           message.put(KEY_HTTP_CONNECTION, connection);
  603           
  604           if (certConstraints != null) {
  605               message.put(CertConstraints.class.getName(), certConstraints);
  606               message.getInterceptorChain().add(CertConstraintsInterceptor.INSTANCE);
  607           }
  608           
  609           // Set the headers on the message according to configured 
  610           // client side policy.
  611           setHeadersByPolicy(message, currentURL, headers);
  612           
  613   
  614           message.setContent(OutputStream.class, 
  615                              new WrappedOutputStream(
  616                                      message, connection,
  617                                      needToCacheRequest, 
  618                                      isChunking,
  619                                      chunkThreshold));
  620           // We are now "ready" to "send" the message. 
  621       }
  622       
  623       public void close(Message msg) throws IOException {
  624           InputStream in = msg.getContent(InputStream.class);
  625           try {
  626               if (in != null) {
  627                   int count = 0;
  628                   byte buffer[] = new byte[1024];
  629                   while (in.read(buffer) != -1
  630                       && count < 25) {
  631                       //don't do anything, we just need to pull off the unread data (like
  632                       //closing tags that we didn't need to read
  633                       
  634                       //however, limit it so we don't read off gigabytes of data we won't use.
  635                       ++count;
  636                   }
  637               } 
  638           } finally {
  639               super.close(msg);
  640           }
  641       }
  642   
  643       /**
  644        * This call must take place before anything is written to the 
  645        * URLConnection. The URLConnection.connect() will be called in order 
  646        * to get the connection information. 
  647        * 
  648        * This method is invoked just after setURLRequestHeaders() from the 
  649        * WrappedOutputStream before it writes data to the URLConnection.
  650        * 
  651        * If trust cannot be established the Trust Decider implemenation
  652        * throws an IOException.
  653        * 
  654        * @param message      The message being sent.
  655        * @throws IOException This exception is thrown if trust cannot be
  656        *                     established by the configured MessageTrustDecider.
  657        * @see MessageTrustDecider
  658        */
  659       private void makeTrustDecision(Message message, HttpURLConnection connection)
  660           throws IOException {
  661           
  662           MessageTrustDecider decider2 = message.get(MessageTrustDecider.class);
  663           if (trustDecider != null || decider2 != null) {
  664               try {
  665                   // We must connect or we will not get the credentials.
  666                   // The call is (said to be) ingored internally if
  667                   // already connected.
  668                   connection.connect();
  669                   URLConnectionInfo info = getConnectionFactory(connection.getURL())
  670                       .getConnectionInfo(connection);
  671                   if (trustDecider != null) {
  672                       trustDecider.establishTrust(
  673                           getConduitName(), 
  674                           info,
  675                           message);
  676                       if (LOG.isLoggable(Level.FINE)) {
  677                           LOG.log(Level.FINE, "Trust Decider "
  678                               + trustDecider.getLogicalName()
  679                               + " considers Conduit "
  680                               + getConduitName() 
  681                               + " trusted.");
  682                       }
  683                   }
  684                   if (decider2 != null) {
  685                       decider2.establishTrust(getConduitName(), 
  686                                               info,
  687                                               message);
  688                       if (LOG.isLoggable(Level.FINE)) {
  689                           LOG.log(Level.FINE, "Trust Decider "
  690                               + decider2.getLogicalName()
  691                               + " considers Conduit "
  692                               + getConduitName() 
  693                               + " trusted.");
  694                       }
  695                   }
  696               } catch (UntrustedURLConnectionIOException untrustedEx) {
  697                   // This cast covers HttpsURLConnection as well.
  698                   ((HttpURLConnection)connection).disconnect();
  699                   if (LOG.isLoggable(Level.FINE)) {
  700                       LOG.log(Level.FINE, "Trust Decider "
  701                           + trustDecider.getLogicalName()
  702                           + " considers Conduit "
  703                           + getConduitName() 
  704                           + " untrusted.", untrustedEx);
  705                   }
  706                   throw untrustedEx;
  707               }
  708           } else {
  709               // This case, when there is no trust decider, a trust
  710               // decision should be a matter of policy.
  711               if (LOG.isLoggable(Level.FINE)) {
  712                   LOG.log(Level.FINE, "No Trust Decider for Conduit '"
  713                       + getConduitName()
  714                       + "'. An afirmative Trust Decision is assumed.");
  715               }
  716           }
  717       }
  718       
  719       /**
  720        * This function sets up a URL based on ENDPOINT_ADDRESS, PATH_INFO,
  721        * and QUERY_STRING properties in the Message. The QUERY_STRING gets
  722        * added with a "?" after the PATH_INFO. If the ENDPOINT_ADDRESS is not
  723        * set on the Message, the endpoint address is taken from the 
  724        * "defaultEndpointURL".
  725        * <p>
  726        * The PATH_INFO is only added to the endpoint address string should 
  727        * the PATH_INFO not equal the end of the endpoint address string.
  728        * 
  729        * @param message The message holds the addressing information.
  730        * 
  731        * @return The full URL specifying the HTTP request to the endpoint.
  732        * 
  733        * @throws MalformedURLException
  734        */
  735       private URL setupURL(Message message) throws MalformedURLException {
  736           String result = (String)message.get(Message.ENDPOINT_ADDRESS);
  737           String pathInfo = (String)message.get(Message.PATH_INFO);
  738           String queryString = (String)message.get(Message.QUERY_STRING);
  739           if (result == null) {
  740               if (pathInfo == null && queryString == null) {
  741                   URL url = getURL();
  742                   message.put(Message.ENDPOINT_ADDRESS, defaultEndpointURLString);
  743                   return url;
  744               }
  745               result = getURL().toString();
  746               message.put(Message.ENDPOINT_ADDRESS, result);
  747           }
  748           
  749           // REVISIT: is this really correct?
  750           if (null != pathInfo && !result.endsWith(pathInfo)) { 
  751               result = result + pathInfo;
  752           }
  753           if (queryString != null) {
  754               result = result + "?" + queryString;
  755           }        
  756           return new URL(result);    
  757       }
  758       
  759       /**
  760        * Retreive the back-channel Destination.
  761        * 
  762        * @return the backchannel Destination (or null if the backchannel is
  763        * built-in)
  764        */
  765       public synchronized Destination getBackChannel() {
  766           if (decoupledDestination == null
  767               &&  getClient().getDecoupledEndpoint() != null) {
  768               setUpDecoupledDestination(); 
  769           }
  770           return decoupledDestination;
  771       }
  772   
  773       /**
  774        * Close the conduit
  775        */
  776       public void close() {
  777           if (defaultEndpointURL != null) {
  778               try {
  779                   URLConnection connect = defaultEndpointURL.openConnection();
  780                   if (connect instanceof HttpURLConnection) {
  781                       ((HttpURLConnection)connect).disconnect();
  782                   }
  783               } catch (IOException ex) {
  784                   //ignore
  785               }
  786               //defaultEndpointURL = null;
  787           }
  788       
  789           // in decoupled case, close response Destination if reference count
  790           // hits zero
  791           //
  792           if (decoupledDestination != null) {
  793               releaseDecoupledDestination();
  794               
  795           }
  796       }
  797   
  798       /**
  799        * @return the default target address
  800        */
  801       protected String getAddress() {
  802           if (defaultEndpointURL != null) {
  803               return defaultEndpointURLString;
  804           } else if (fromEndpointReferenceType) {
  805               return getTarget().getAddress().getValue();
  806           }
  807           return endpointInfo.getAddress();
  808       }
  809   
  810       /**
  811        * @return the default target URL
  812        */
  813       protected URL getURL() throws MalformedURLException {
  814           if (defaultEndpointURL == null) {
  815               return getURL(true);
  816           } 
  817           return defaultEndpointURL;
  818       }
  819   
  820       /**
  821        * @param createOnDemand create URL on-demand if null
  822        * @return the default target URL
  823        */
  824       protected synchronized URL getURL(boolean createOnDemand)
  825           throws MalformedURLException {
  826           if (defaultEndpointURL == null && createOnDemand) {
  827               if (fromEndpointReferenceType && getTarget().getAddress().getValue() != null) {
  828                   defaultEndpointURL = new URL(this.getTarget().getAddress().getValue());
  829                   defaultEndpointURLString = defaultEndpointURL.toExternalForm();
  830                   return defaultEndpointURL;
  831               }
  832               if (endpointInfo.getAddress() == null) {
  833                   throw new MalformedURLException("Invalid address. Endpoint address cannot be null.");
  834               }
  835               defaultEndpointURL = new URL(endpointInfo.getAddress());
  836               defaultEndpointURLString = defaultEndpointURL.toExternalForm();
  837           }
  838           return defaultEndpointURL;
  839       }
  840   
  841       /**
  842        * While extracting the Message.PROTOCOL_HEADERS property from the Message,
  843        * this call ensures that the Message.PROTOCOL_HEADERS property is
  844        * set on the Message. If it is not set, an empty map is placed there, and
  845        * then returned.
  846        * 
  847        * @param message The outbound message
  848        * @return The PROTOCOL_HEADERS map
  849        */
  850       private Map<String, List<String>> getSetProtocolHeaders(Message message) {
  851           Map<String, List<String>> headers =
  852               CastUtils.cast((Map<?, ?>)message.get(Message.PROTOCOL_HEADERS));        
  853           if (null == headers) {
  854               headers = new LinkedHashMap<String, List<String>>();
  855           } else if (headers instanceof HashMap) {
  856               headers = new LinkedHashMap<String, List<String>>(headers);
  857           }
  858           message.put(Message.PROTOCOL_HEADERS, headers);
  859           return headers;
  860       }
  861       
  862       
  863       /**
  864        * This procedure sets the URLConnection request properties
  865        * from the PROTOCOL_HEADERS in the message.
  866        */
  867       private void transferProtocolHeadersToURLConnection(
  868           Message message,
  869           URLConnection connection
  870       ) {
  871           Map<String, List<String>> headers = getSetProtocolHeaders(message);
  872           for (String header : headers.keySet()) {
  873               List<String> headerList = headers.get(header);
  874               if (HttpHeaderHelper.CONTENT_TYPE.equalsIgnoreCase(header)) {
  875                   continue;
  876               }
  877               if (HttpHeaderHelper.COOKIE.equalsIgnoreCase(header)) {
  878                   for (String s : headerList) {
  879                       connection.addRequestProperty(HttpHeaderHelper.COOKIE, s);
  880                   }
  881               } else {
  882                   StringBuilder b = new StringBuilder();
  883                   for (int i = 0; i < headerList.size(); i++) {
  884                       b.append(headerList.get(i));
  885                       if (i + 1 < headerList.size()) {
  886                           b.append(',');
  887                       }
  888                   }
  889                   connection.setRequestProperty(header, b.toString());
  890               }
  891           }
  892           if (!connection.getRequestProperties().containsKey("User-Agent")) {
  893               connection.addRequestProperty("User-Agent", Version.getCompleteVersionString());
  894           }
  895       }
  896       
  897       /**
  898        * This procedure logs the PROTOCOL_HEADERS from the 
  899        * Message at the specified logging level.
  900        * 
  901        * @param level   The Logging Level.
  902        * @param headers The Message protocol headers.
  903        */
  904       private void logProtocolHeaders(
  905           Level   level,
  906           Message message
  907       ) {
  908           Map<String, List<String>> headers = getSetProtocolHeaders(message);
  909           for (String header : headers.keySet()) {
  910               List<String> headerList = headers.get(header);
  911               for (String value : headerList) {
  912                   LOG.log(level, header + ": " + value);
  913               }
  914           }
  915       }
  916       
  917       /**
  918        * Put the headers from Message.PROTOCOL_HEADERS headers into the URL
  919        * connection.
  920        * Note, this does not mean they immediately get written to the output
  921        * stream or the wire. They just just get set on the HTTP request.
  922        * 
  923        * @param message The outbound message.
  924        * @throws IOException
  925        */
  926       private void setURLRequestHeaders(Message message) throws IOException {
  927           HttpURLConnection connection = 
  928               (HttpURLConnection)message.get(KEY_HTTP_CONNECTION);
  929   
  930           String ct  = (String)message.get(Message.CONTENT_TYPE);
  931           String enc = (String)message.get(Message.ENCODING);
  932   
  933           if (null != ct) {
  934               if (enc != null 
  935                   && ct.indexOf("charset=") == -1
  936                   && !ct.toLowerCase().contains("multipart/related")) {
  937                   ct = ct + "; charset=" + enc;
  938               }
  939           } else if (enc != null) {
  940               ct = "text/xml; charset=" + enc;
  941           } else {
  942               ct = "text/xml";
  943           }
  944           connection.setRequestProperty(HttpHeaderHelper.CONTENT_TYPE, ct);
  945           
  946           if (LOG.isLoggable(Level.FINE)) {
  947               LOG.fine("Sending "
  948                   + connection.getRequestMethod() 
  949                   + " Message with Headers to " 
  950                              + connection.getURL()
  951                   + " Conduit :"
  952                   + getConduitName()
  953                   + "\nContent-Type: " + ct + "\n");
  954               logProtocolHeaders(Level.FINE, message);
  955           }
  956           
  957           transferProtocolHeadersToURLConnection(message, connection);
  958           
  959       }
  960       
  961       /**
  962        * Set up the decoupled Destination if necessary.
  963        */
  964       private void setUpDecoupledDestination() {        
  965           EndpointReferenceType reference =
  966               EndpointReferenceUtils.getEndpointReference(
  967                   getClient().getDecoupledEndpoint());
  968           if (reference != null) {
  969               String decoupledAddress = reference.getAddress().getValue();
  970               LOG.info("creating decoupled endpoint: " + decoupledAddress);
  971               try {
  972                   decoupledDestination = getDestination(decoupledAddress);
  973                   duplicateDecoupledDestination();
  974               } catch (Exception e) {
  975                   // REVISIT move message to localizable Messages.properties
  976                   LOG.log(Level.WARNING, 
  977                           "decoupled endpoint creation failed: ", e);
  978               }
  979           }
  980       }
  981   
  982       /**
  983        * @param address the address
  984        * @return a Destination for the address
  985        */
  986       private Destination getDestination(String address) throws IOException {
  987           Destination destination = null;
  988           DestinationFactoryManager factoryManager =
  989               bus.getExtension(DestinationFactoryManager.class);
  990           DestinationFactory factory =
  991               factoryManager.getDestinationFactoryForUri(address);
  992           if (factory != null) {
  993               EndpointInfo ei = new EndpointInfo();
  994               ei.setAddress(address);
  995               destination = factory.getDestination(ei);
  996               decoupledObserver = new InterposedMessageObserver();
  997               destination.setMessageObserver(decoupledObserver);
  998           }
  999           return destination;
 1000       }
 1001       
 1002       /**
 1003        * @return the decoupled observer
 1004        */
 1005       protected MessageObserver getDecoupledObserver() {
 1006           return decoupledObserver;
 1007       }
 1008       
 1009       private synchronized void duplicateDecoupledDestination() {
 1010           decoupledDestinationRefCount++;
 1011       }
 1012       
 1013       private synchronized void releaseDecoupledDestination() {
 1014           if (--decoupledDestinationRefCount == 0) {
 1015               LOG.log(Level.FINE, "shutting down decoupled destination");
 1016               decoupledDestination.shutdown();
 1017   
 1018               //this way we can release the port of decoupled destination
 1019               decoupledDestination.setMessageObserver(null);
 1020           }
 1021       }
 1022       
 1023       /**
 1024        * This predicate returns true iff the exchange indicates 
 1025        * a oneway MEP.
 1026        * 
 1027        * @param exchange The exchange in question
 1028        */
 1029       private boolean isOneway(Exchange exchange) {
 1030           return exchange != null && exchange.isOneWay();
 1031       }
 1032       
 1033       /**
 1034        * @return true if expecting a decoupled response
 1035        */
 1036       private boolean isDecoupled() {
 1037           return decoupledDestination != null;
 1038       }
 1039       
 1040       /**
 1041        * Get an input stream containing the partial response if one is present.
 1042        * 
 1043        * @param connection the connection in question
 1044        * @param responseCode the response code
 1045        * @return an input stream if a partial response is pending on the connection 
 1046        */
 1047       protected static InputStream getPartialResponse(
 1048           HttpURLConnection connection,
 1049           int responseCode
 1050       ) throws IOException {
 1051           InputStream in = null;
 1052           if (responseCode == HttpURLConnection.HTTP_ACCEPTED
 1053               || responseCode == HttpURLConnection.HTTP_OK) {
 1054               if (connection.getContentLength() > 0) {
 1055                   in = connection.getInputStream();
 1056               } else if (hasChunkedResponse(connection) 
 1057                          || hasEofTerminatedResponse(connection)) {
 1058                   // ensure chunked or EOF-terminated response is non-empty
 1059                   in = getNonEmptyContent(connection);        
 1060               }
 1061           }
 1062           return in;
 1063       }
 1064       
 1065       /**
 1066        * @param connection the given HttpURLConnection
 1067        * @return true iff the connection has a chunked response pending
 1068        */
 1069       private static boolean hasChunkedResponse(HttpURLConnection connection) {
 1070           return HttpHeaderHelper.CHUNKED.equalsIgnoreCase(
 1071                      connection.getHeaderField(HttpHeaderHelper.TRANSFER_ENCODING));
 1072       }
 1073       
 1074       /**
 1075        * @param connection the given HttpURLConnection
 1076        * @return true iff the connection has a chunked response pending
 1077        */
 1078       private static boolean hasEofTerminatedResponse(
 1079           HttpURLConnection connection
 1080       ) {
 1081           return HttpHeaderHelper.CLOSE.equalsIgnoreCase(
 1082                      connection.getHeaderField(HttpHeaderHelper.CONNECTION));
 1083       }
 1084   
 1085       /**
 1086        * @param connection the given HttpURLConnection
 1087        * @return an input stream containing the response content if non-empty
 1088        */
 1089       private static InputStream getNonEmptyContent(
 1090           HttpURLConnection connection
 1091       ) {
 1092           InputStream in = null;
 1093           try {
 1094               PushbackInputStream pin = 
 1095                   new PushbackInputStream(connection.getInputStream());
 1096               int c = pin.read();
 1097               if (c != -1) {
 1098                   pin.unread((byte)c);
 1099                   in = pin;
 1100               }
 1101           } catch (IOException ioe) {
 1102               // ignore
 1103           }    
 1104           return in;
 1105       }
 1106   
 1107       /**
 1108        * This method returns the Proxy server should it be set on the 
 1109        * Client Side Policy.
 1110        * 
 1111        * @return The proxy server or null, if not set.
 1112        */
 1113       private Proxy getProxy(HTTPClientPolicy policy) {
 1114           Proxy proxy = null; 
 1115           if (policy != null 
 1116               && policy.isSetProxyServer()
 1117               && !StringUtils.isEmpty(policy.getProxyServer())) {
 1118               proxy = new Proxy(
 1119                       Proxy.Type.valueOf(policy.getProxyServerType().toString()),
 1120                       new InetSocketAddress(policy.getProxyServer(),
 1121                                             policy.getProxyServerPort()));
 1122           }
 1123           return proxy;
 1124       }
 1125   
 1126       /**
 1127        * This call places HTTP Header strings into the headers that are relevant
 1128        * to the Authorization policies that are set on this conduit by 
 1129        * configuration.
 1130        * <p> 
 1131        * An AuthorizationPolicy may also be set on the message. If so, those 
 1132        * policies are merged. A user name or password set on the messsage 
 1133        * overrides settings in the AuthorizationPolicy is retrieved from the
 1134        * configuration.
 1135        * <p>
 1136        * The precedence is as follows:
 1137        * 1. AuthorizationPolicy that is set on the Message, if exists.
 1138        * 2. Authorization from AuthSupplier, if exists.
 1139        * 3. AuthorizationPolicy set/configured for conduit.
 1140        * 
 1141        * REVISIT: Since the AuthorizationPolicy is set on the message by class, then
 1142        * how does one override the ProxyAuthorizationPolicy which is the same 
 1143        * type?
 1144        * 
 1145        * @param message
 1146        * @param headers
 1147        */
 1148       private void setHeadersByAuthorizationPolicy(
 1149               Message message,
 1150               URL url,
 1151               Map<String, List<String>> headers
 1152       ) {
 1153           AuthorizationPolicy authPolicy = getAuthorization();
 1154           AuthorizationPolicy newPolicy = message.get(AuthorizationPolicy.class);
 1155           
 1156           String authString = null;
 1157           if (authSupplier != null 
 1158               && (newPolicy == null
 1159                   || (!"Basic".equals(newPolicy.getAuthorizationType())
 1160                       && newPolicy.getAuthorization() == null))) {
 1161               authString = (String)message.get("AUTH_VALUE");
 1162               if (authString == null) {
 1163                   authString = authSupplier.getPreemptiveAuthorization(
 1164                       this, url, message);
 1165               } else {
 1166                   message.remove("AUTH_VALUE");
 1167               }
 1168               if (authString != null) {
 1169                   headers.put("Authorization",
 1170                               createMutableList(authString));
 1171               }
 1172               return;
 1173           }
 1174           String userName = null;
 1175           String passwd = null;
 1176           if (null != newPolicy) {
 1177               userName = newPolicy.getUserName();
 1178               passwd = newPolicy.getPassword();
 1179           }
 1180   
 1181           if (userName == null 
 1182               && authPolicy != null && authPolicy.isSetUserName()) {
 1183               userName = authPolicy.getUserName();
 1184           }
 1185           if (userName != null) {
 1186               if (passwd == null 
 1187                   && authPolicy != null && authPolicy.isSetPassword()) {
 1188                   passwd = authPolicy.getPassword();
 1189               }
 1190               setBasicAuthHeader(userName, passwd, headers);
 1191           } else if (authPolicy != null 
 1192                   && authPolicy.isSetAuthorizationType() 
 1193                   && authPolicy.isSetAuthorization()) {
 1194               String type = authPolicy.getAuthorizationType();
 1195               type += " ";
 1196               type += authPolicy.getAuthorization();
 1197               headers.put("Authorization",
 1198                           createMutableList(type));
 1199           }
 1200           AuthorizationPolicy proxyAuthPolicy = getProxyAuthorization();
 1201           if (proxyAuthPolicy != null && proxyAuthPolicy.isSetUserName()) {
 1202               userName = proxyAuthPolicy.getUserName();
 1203               if (userName != null) {
 1204                   passwd = "";
 1205                   if (proxyAuthPolicy.isSetPassword()) {
 1206                       passwd = proxyAuthPolicy.getPassword();
 1207                   }
 1208                   setProxyBasicAuthHeader(userName, passwd, headers);
 1209               } else if (proxyAuthPolicy.isSetAuthorizationType() 
 1210                          && proxyAuthPolicy.isSetAuthorization()) {
 1211                   String type = proxyAuthPolicy.getAuthorizationType();
 1212                   type += " ";
 1213                   type += proxyAuthPolicy.getAuthorization();
 1214                   headers.put("Proxy-Authorization",
 1215                               createMutableList(type));
 1216               }
 1217           }
 1218       }
 1219       private static List<String> createMutableList(String val) {
 1220           return new ArrayList<String>(Arrays.asList(new String[] {val}));
 1221       }
 1222       /**
 1223        * This call places HTTP Header strings into the headers that are relevant
 1224        * to the ClientPolicy that is set on this conduit by configuration.
 1225        * 
 1226        * REVISIT: A cookie is set statically from configuration? 
 1227        */
 1228       private void setHeadersByClientPolicy(
 1229           Message message,
 1230           Map<String, List<String>> headers
 1231       ) {
 1232           HTTPClientPolicy policy = getClient(message);
 1233           if (policy == null) {
 1234               return;
 1235           }
 1236           if (policy.isSetCacheControl()) {
 1237               headers.put("Cache-Control",
 1238                           createMutableList(policy.getCacheControl().value()));
 1239           }
 1240           if (policy.isSetHost()) {
 1241               headers.put("Host",
 1242                           createMutableList(policy.getHost()));
 1243           }
 1244           if (policy.isSetConnection()) {
 1245               headers.put("Connection",
 1246                           createMutableList(policy.getConnection().value()));
 1247           }
 1248           if (policy.isSetAccept()) {
 1249               headers.put("Accept",
 1250                           createMutableList(policy.getAccept()));
 1251           } else if (!headers.containsKey("Accept")) {
 1252               headers.put("Accept", createMutableList("*/*"));
 1253           }
 1254           if (policy.isSetAcceptEncoding()) {
 1255               headers.put("Accept-Encoding",
 1256                           createMutableList(policy.getAcceptEncoding()));
 1257           }
 1258           if (policy.isSetAcceptLanguage()) {
 1259               headers.put("Accept-Language",
 1260                           createMutableList(policy.getAcceptLanguage()));
 1261           }
 1262           if (policy.isSetContentType()) {
 1263               message.put(Message.CONTENT_TYPE, policy.getContentType());
 1264           }
 1265           if (policy.isSetCookie()) {
 1266               headers.put("Cookie",
 1267                           createMutableList(policy.getCookie()));
 1268           }
 1269           if (policy.isSetBrowserType()) {
 1270               headers.put("BrowserType",
 1271                           createMutableList(policy.getBrowserType()));
 1272           }
 1273           if (policy.isSetReferer()) {
 1274               headers.put("Referer",
 1275                           createMutableList(policy.getReferer()));
 1276           }
 1277       }
 1278   
 1279       /**
 1280        * This call places HTTP Header strings into the headers that are relevant
 1281        * to the polices that are set on this conduit by configuration for the
 1282        * ClientPolicy and AuthorizationPolicy.
 1283        * 
 1284        * 
 1285        * @param message The outgoing message.
 1286        * @param url     The URL the message is going to.
 1287        * @param headers The headers in the outgoing message.
 1288        */
 1289       private void setHeadersByPolicy(
 1290           Message message,
 1291           URL     url,
 1292           Map<String, List<String>> headers
 1293       ) {
 1294           setHeadersByAuthorizationPolicy(message, url, headers);
 1295           setHeadersByClientPolicy(message, headers);
 1296       }
 1297       
 1298       /**
 1299        * This is part of the Configurable interface which retrieves the 
 1300        * configuration from spring injection.
 1301        */
 1302       // REVISIT:What happens when the endpoint/bean name is null?
 1303       public String getBeanName() {
 1304           if (endpointInfo.getName() != null) {
 1305               return endpointInfo.getName().toString() + ".http-conduit";
 1306           }
 1307           return null;
 1308       }
 1309   
 1310       /**
 1311        * This method gets the Authorization Policy that was configured or 
 1312        * explicitly set for this HTTPConduit.
 1313        */
 1314       public AuthorizationPolicy getAuthorization() {
 1315           return authorizationPolicy;
 1316       }
 1317   
 1318       /**
 1319        * This method is used to set the Authorization Policy for this conduit.
 1320        * Using this method will override any Authorization Policy set in 
 1321        * configuration.
 1322        */
 1323       public void setAuthorization(AuthorizationPolicy authorization) {
 1324           this.authorizationPolicy = authorization;
 1325       }
 1326       
 1327       public HTTPClientPolicy getClient(Message message) {
 1328           return PolicyUtils.getClient(message, clientSidePolicy);
 1329       }
 1330   
 1331       /**
 1332        * This method retrieves the Client Side Policy set/configured for this
 1333        * HTTPConduit.
 1334        */
 1335       public HTTPClientPolicy getClient() {
 1336           return clientSidePolicy;
 1337       }
 1338   
 1339       /**
 1340        * This method sets the Client Side Policy for this HTTPConduit. Using this
 1341        * method will override any HTTPClientPolicy set in configuration.
 1342        */
 1343       public void setClient(HTTPClientPolicy client) {
 1344           this.clientSidePolicy = client;
 1345       }
 1346   
 1347       /**
 1348        * This method retrieves the Proxy Authorization Policy for a proxy that is
 1349        * set/configured for this HTTPConduit.
 1350        */
 1351       public ProxyAuthorizationPolicy getProxyAuthorization() {
 1352           return proxyAuthorizationPolicy;
 1353       }
 1354   
 1355       /**
 1356        * This method sets the Proxy Authorization Policy for a specified proxy. 
 1357        * Using this method overrides any Authorization Policy for the proxy 
 1358        * that is set in the configuration.
 1359        */
 1360       public void setProxyAuthorization(
 1361               ProxyAuthorizationPolicy proxyAuthorization
 1362       ) {
 1363           this.proxyAuthorizationPolicy = proxyAuthorization;
 1364       }
 1365   
 1366       /**
 1367        * This method returns the TLS Client Parameters that is set/configured
 1368        * for this HTTPConduit.
 1369        */
 1370       public TLSClientParameters getTlsClientParameters() {
 1371           return tlsClientParameters;
 1372       }
 1373   
 1374       /**
 1375        * This method sets the TLS Client Parameters for this HTTPConduit.
 1376        * Using this method overrides any TLS Client Parameters that is configured
 1377        * for this HTTPConduit.
 1378        */
 1379       public void setTlsClientParameters(TLSClientParameters params) {
 1380           this.tlsClientParameters = params;
 1381           if (this.tlsClientParameters != null) {
 1382               if (LOG.isLoggable(Level.FINE)) {
 1383                   LOG.log(Level.FINE, "Conduit '" + getConduitName()
 1384                       + "' has been (re) configured for TLS "
 1385                       + "keyManagers " + Arrays.toString(tlsClientParameters.getKeyManagers())
 1386                       + "trustManagers " + Arrays.toString(tlsClientParameters.getTrustManagers())
 1387                       + "secureRandom " + tlsClientParameters.getSecureRandom());
 1388               }
 1389               CertificateConstraintsType constraints = params.getCertConstraints();
 1390               if (constraints != null) {
 1391                   certConstraints = CertConstraintsJaxBUtils.createCertConstraints(constraints);
 1392               }
 1393           } else {
 1394               if (LOG.isLoggable(Level.FINE)) {
 1395                   LOG.log(Level.FINE, "Conduit '" + getConduitName()
 1396                       + "' has been (re)configured for plain http.");
 1397               }
 1398           }
 1399           // If this is called after the HTTPTransportFactory called 
 1400           // finalizeConfig, we need to update the connection factory.
 1401           if (configFinalized) {
 1402               retrieveConnectionFactory(getAddress());
 1403           }
 1404       }
 1405   
 1406       /**
 1407        * This method gets the Trust Decider that was set/configured for this 
 1408        * HTTPConduit.
 1409        * @return The Message Trust Decider or null.
 1410        */
 1411       public MessageTrustDecider getTrustDecider() {
 1412           return this.trustDecider;
 1413       }
 1414       
 1415       /**
 1416        * This method sets the Trust Decider for this HTTP Conduit.
 1417        * Using this method overrides any trust decider configured for this 
 1418        * HTTPConduit.
 1419        */
 1420       public void setTrustDecider(MessageTrustDecider decider) {
 1421           this.trustDecider = decider;
 1422       }
 1423   
 1424       /**
 1425        * This method gets the Auth Supplier that was set/configured for this 
 1426        * HTTPConduit.
 1427        * @return The Auth Supplier or null.
 1428        */
 1429       public HttpAuthSupplier getAuthSupplier() {
 1430           return this.authSupplier;
 1431       }
 1432       
 1433       public void setAuthSupplier(HttpAuthSupplier supplier) {
 1434           this.authSupplier = supplier;
 1435       }
 1436       
 1437       /**
 1438        * This function processes any retransmits at the direction of redirections
 1439        * or "unauthorized" responses.
 1440        * <p>
 1441        * If the request was not retransmitted, it returns the given connection. 
 1442        * If the request was retransmitted, it returns the new connection on
 1443        * which the request was sent.
 1444        * 
 1445        * @param connection   The active URL connection.
 1446        * @param message      The outgoing message.
 1447        * @param cachedStream The cached request.
 1448        * @return
 1449        * @throws IOException
 1450        */
 1451       private HttpURLConnection processRetransmit(
 1452           HttpURLConnection connection,
 1453           Message message,
 1454           CacheAndWriteOutputStream cachedStream
 1455       ) throws IOException {
 1456   
 1457           int responseCode = connection.getResponseCode();
 1458           if ((message != null) && (message.getExchange() != null)) {
 1459               message.getExchange().put(Message.RESPONSE_CODE, responseCode);
 1460           }
 1461           
 1462           // Process Redirects first.
 1463           switch(responseCode) {
 1464           case HttpURLConnection.HTTP_MOVED_PERM:
 1465           case HttpURLConnection.HTTP_MOVED_TEMP:
 1466               connection = 
 1467                   redirectRetransmit(connection, message, cachedStream);
 1468               break;
 1469           case HttpURLConnection.HTTP_UNAUTHORIZED:
 1470               connection = 
 1471                   authorizationRetransmit(connection, message, cachedStream);
 1472               break;
 1473           default:
 1474               break;
 1475           }
 1476           return connection;
 1477       }
 1478   
 1479       /**
 1480        * This method performs a redirection retransmit in response to
 1481        * a 302 or 305 response code.
 1482        * 
 1483        * @param connection   The active URL connection
 1484        * @param message      The outbound message.
 1485        * @param cachedStream The cached request.
 1486        * @return This method returns the new HttpURLConnection if
 1487        *         redirected. If it cannot be redirected for some reason
 1488        *         the same connection is returned.
 1489        *         
 1490        * @throws IOException
 1491        */
 1492       private HttpURLConnection redirectRetransmit(
 1493           HttpURLConnection connection,
 1494           Message message,
 1495           CacheAndWriteOutputStream cachedStream
 1496       ) throws IOException {
 1497           
 1498           // If we are not redirecting by policy, then we don't.
 1499           if (!getClient(message).isAutoRedirect()) {
 1500               return connection;
 1501           }
 1502   
 1503           // We keep track of the redirections for redirect loop protection.
 1504           Set<String> visitedURLs = getSetVisitedURLs(message);
 1505           
 1506           String lastURL = connection.getURL().toString();
 1507           visitedURLs.add(lastURL);
 1508           
 1509           String newURL = extractLocation(connection.getHeaderFields());
 1510           if (newURL != null) {
 1511               // See if we are being redirected in a loop as best we can,
 1512               // using string equality on URL.
 1513               if (visitedURLs.contains(newURL)) {
 1514                   // We are in a redirect loop; -- bail
 1515                   if (LOG.isLoggable(Level.INFO)) {
 1516                       LOG.log(Level.INFO, "Redirect loop detected on Conduit \"" 
 1517                           + getConduitName() 
 1518                           + "\" on '" 
 1519                           + newURL
 1520                           + "'");
 1521                   }
 1522                   throw new IOException("Redirect loop detected on Conduit \"" 
 1523                                         + getConduitName() 
 1524                                         + "\" on '" 
 1525                                         + newURL
 1526                                         + "'");
 1527               }
 1528               // We are going to redirect.
 1529               // Remove any Server Authentication Information for the previous
 1530               // URL.
 1531               Map<String, List<String>> headers = 
 1532                                   getSetProtocolHeaders(message);
 1533               headers.remove("Authorization");
 1534               headers.remove("Proxy-Authorization");
 1535               
 1536               URL url = new URL(newURL);
 1537               
 1538               // If user configured this Conduit with preemptive authorization
 1539               // it is meant to make it to the end. (Too bad that information
 1540               // went to every URL along the way, but that's what the user 
 1541               // wants!
 1542               // TODO: Make this issue a security release note.
 1543               setHeadersByAuthorizationPolicy(message, url, headers);
 1544               
 1545               connection = retransmit(
 1546                       connection, url, message, cachedStream);
 1547           }
 1548           return connection;
 1549       }
 1550   
 1551       /**
 1552        * This function gets the Set of URLs on the message that is used to 
 1553        * keep track of the URLs that were used in getting authorization 
 1554        * information.
 1555        *
 1556        * @param message The message where the Set of URLs is stored.
 1557        * @return The modifiable set of URLs that were visited.
 1558        */
 1559       private Set<String> getSetAuthoriationURLs(Message message) {
 1560           @SuppressWarnings("unchecked")
 1561           Set<String> authURLs = (Set<String>) message.get(KEY_AUTH_URLS);
 1562           if (authURLs == null) {
 1563               authURLs = new HashSet<String>();
 1564               message.put(KEY_AUTH_URLS, authURLs);
 1565           }
 1566           return authURLs;
 1567       }
 1568   
 1569       /**
 1570        * This function get the set of URLs on the message that is used to keep
 1571        * track of the URLs that were visited in redirects.
 1572        * 
 1573        * If it is not set on the message, an new empty set is stored.
 1574        * @param message The message where the Set is stored.
 1575        * @return The modifiable set of URLs that were visited.
 1576        */
 1577       private Set<String> getSetVisitedURLs(Message message) {
 1578           @SuppressWarnings("unchecked")
 1579           Set<String> visitedURLs = (Set<String>) message.get(KEY_VISITED_URLS);
 1580           if (visitedURLs == null) {
 1581               visitedURLs = new HashSet<String>();
 1582               message.put(KEY_VISITED_URLS, visitedURLs);
 1583           }
 1584           return visitedURLs;
 1585       }
 1586       
 1587       /**
 1588        * This method performs a retransmit for authorization information.
 1589        * 
 1590        * @param connection The currently active connection.
 1591        * @param message The outbound message.
 1592        * @param cachedStream The cached request.
 1593        * @return A new connection if retransmitted. If not retransmitted
 1594        *         then this method returns the same connection.
 1595        * @throws IOException
 1596        */
 1597       private HttpURLConnection authorizationRetransmit(
 1598           HttpURLConnection connection,
 1599           Message message, 
 1600           CacheAndWriteOutputStream cachedStream
 1601       ) throws IOException {
 1602   
 1603           // If we don't have a dynamic supply of user pass, then
 1604           // we don't retransmit. We just die with a Http 401 response.
 1605           if (authSupplier == null) {
 1606               String auth = connection.getHeaderField("WWW-Authenticate");
 1607               if (auth.startsWith("Digest ")) {
 1608                   authSupplier = new DigestAuthSupplier();
 1609               } else {
 1610                   return connection;
 1611               }
 1612           }
 1613           
 1614           URL currentURL = connection.getURL();
 1615           
 1616           String realm = extractAuthorizationRealm(connection.getHeaderFields());
 1617           
 1618           Set<String> authURLs = getSetAuthoriationURLs(message);
 1619           
 1620           // If we have been here (URL & Realm) before for this particular message
 1621           // retransmit, it means we have already supplied information
 1622           // which must have been wrong, or we wouldn't be here again.
 1623           // Otherwise, the server may be 401 looping us around the realms.
 1624           if (authURLs.contains(currentURL.toString() + realm)) {
 1625   
 1626               if (LOG.isLoggable(Level.INFO)) {
 1627                   LOG.log(Level.INFO, "Authorization loop detected on Conduit \""
 1628                       + getConduitName()
 1629                       + "\" on URL \""
 1630                       + "\" with realm \""
 1631                       + realm
 1632                       + "\"");
 1633               }
 1634                       
 1635               throw new IOException("Authorization loop detected on Conduit \"" 
 1636                                     + getConduitName() 
 1637                                     + "\" on URL \""
 1638                                     + "\" with realm \""
 1639                                     + realm
 1640                                     + "\"");
 1641           }
 1642           
 1643           String up = 
 1644               authSupplier.getAuthorizationForRealm(
 1645                   this, currentURL, message, realm, connection.getHeaderField("WWW-Authenticate"));
 1646           
 1647           // No user pass combination. We give up.
 1648           if (up == null) {
 1649               return connection;
 1650           }
 1651           
 1652           // Register that we have been here before we go.
 1653           authURLs.add(currentURL.toString() + realm);
 1654           
 1655           Map<String, List<String>> headers = getSetProtocolHeaders(message);
 1656           headers.put("Authorization",
 1657                       createMutableList(up));
 1658           return retransmit(
 1659                   connection, currentURL, message, cachedStream);
 1660       }
 1661       
 1662       /**
 1663        * This method retransmits the request.
 1664        * 
 1665        * @param connection The currently active connection.
 1666        * @param newURL     The newURL to connection to.
 1667        * @param message    The outbound message.
 1668        * @param stream     The cached request.
 1669        * @return           This function returns a new connection if
 1670        *                   retransmitted, otherwise it returns the given
 1671        *                   connection.
 1672        *                   
 1673        * @throws IOException
 1674        */
 1675       private HttpURLConnection retransmit(
 1676               HttpURLConnection  connection,
 1677               URL                newURL,
 1678               Message            message, 
 1679               CacheAndWriteOutputStream stream
 1680       ) throws IOException {
 1681           
 1682           // Disconnect the old, and in with the new.
 1683           connection.disconnect();
 1684           
 1685           HTTPClientPolicy cp = getClient(message);
 1686           connection = getConnectionFactory(newURL).createConnection(getProxy(cp), newURL);
 1687           connection.setDoOutput(true);        
 1688           // TODO: using Message context to deceided HTTP send properties
 1689           connection.setConnectTimeout((int)cp.getConnectionTimeout());
 1690           connection.setReadTimeout((int)cp.getReceiveTimeout());
 1691           connection.setUseCaches(false);
 1692           connection.setInstanceFollowRedirects(false);
 1693   
 1694           // If the HTTP_REQUEST_METHOD is not set, the default is "POST".
 1695           String httpRequestMethod = 
 1696               (String)message.get(Message.HTTP_REQUEST_METHOD);
 1697   
 1698           if (null != httpRequestMethod) {
 1699               connection.setRequestMethod(httpRequestMethod);
 1700           } else {
 1701               connection.setRequestMethod("POST");
 1702           }
 1703           message.put(KEY_HTTP_CONNECTION, connection);
 1704   
 1705           connection.setFixedLengthStreamingMode(stream.size());
 1706           
 1707           // Need to set the headers before the trust decision
 1708           // because they are set before the connect().
 1709           setURLRequestHeaders(message);
 1710           
 1711           //
 1712           // This point is where the trust decision is made because the
 1713           // Sun implementation of URLConnection will not let us 
 1714           // set/addRequestProperty after a connect() call, and 
 1715           // makeTrustDecision needs to make a connect() call to
 1716           // make sure the proper information is available.
 1717           // 
 1718           makeTrustDecision(message, connection);
 1719   
 1720           // If this is a GET method we must not touch the output
 1721           // stream as this automagically turns the request into a POST.
 1722           if (connection.getRequestMethod().equals("GET")) {
 1723               return connection;
 1724           }
 1725           
 1726           // Trust is okay, write the cached request
 1727           OutputStream out = connection.getOutputStream();
 1728           stream.writeCacheTo(out);
 1729           
 1730           if (LOG.isLoggable(Level.FINE)) {
 1731               LOG.fine("Conduit \""
 1732                        + getConduitName() 
 1733                        + "\" Retransmit message to: " 
 1734                        + connection.getURL()
 1735                        + ": "
 1736                        + new String(stream.getBytes()));
 1737           }
 1738           return connection;
 1739       }
 1740   
 1741       /**
 1742        * This function extracts the authorization realm from the 
 1743        * "WWW-Authenticate" Http response header.
 1744        * 
 1745        * @param headers The Http Response Headers
 1746        * @return The realm, or null if it is non-existent.
 1747        */
 1748       private String extractAuthorizationRealm(
 1749               Map<String, List<String>> headers
 1750       ) {
 1751           List<String> auth = headers.get("WWW-Authenticate");
 1752           if (auth != null) {
 1753               for (String a : auth) {
 1754                   int idx = a.indexOf("realm=");
 1755                   if (idx != -1) {
 1756                       a = a.substring(idx + 6);
 1757                       if (a.charAt(0) == '"') {
 1758                           a = a.substring(1, a.indexOf('"', 1));
 1759                       } else if (a.contains(",")) {
 1760                           a = a.substring(0, a.indexOf(','));
 1761                       }
 1762                       return a;
 1763                   }
 1764               }
 1765           }
 1766           return null;
 1767       }
 1768       
 1769       /**
 1770        * This method extracts the value of the "Location" Http
 1771        * Response header.
 1772        * 
 1773        * @param headers The Http response headers.
 1774        * @return The value of the "Location" header, null if non-existent.
 1775        */
 1776       private String extractLocation(
 1777               Map<String, List<String>> headers
 1778       ) {
 1779           
 1780           for (Map.Entry<String, List<String>> head : headers.entrySet()) {
 1781               if ("Location".equalsIgnoreCase(head.getKey())) {
 1782                   List<String> locs = head.getValue();
 1783                   if (locs != null && locs.size() > 0) {
 1784                       return locs.get(0);
 1785                   }                
 1786               }
 1787           }
 1788           return null;
 1789       }
 1790   
 1791       /**
 1792        * This procedure sets the "Authorization" header with the 
 1793        * BasicAuth token, which is Base64 encoded.
 1794        * 
 1795        * @param userid   The user's id, which cannot be null.
 1796        * @param password The password, it may be null.
 1797        * 
 1798        * @param headers  The headers map that gets the "Authorization" header set.
 1799        */
 1800       private void setBasicAuthHeader(
 1801           String                    userid,
 1802           String                    password,
 1803           Map<String, List<String>> headers
 1804       ) {
 1805           String userpass = userid;
 1806   
 1807           userpass += ":";
 1808           if (password != null) {
 1809               userpass += password;
 1810           }
 1811           String token = Base64Utility.encode(userpass.getBytes());
 1812           headers.put("Authorization",
 1813                       createMutableList("Basic " + token));
 1814       }
 1815   
 1816       /**
 1817        * This procedure sets the "ProxyAuthorization" header with the 
 1818        * BasicAuth token, which is Base64 encoded.
 1819        * 
 1820        * @param userid   The user's id, which cannot be null.
 1821        * @param password The password, it may be null.
 1822        * 
 1823        * @param headers The headers map that gets the "Proxy-Authorization" 
 1824        *                header set.
 1825        */
 1826       private void setProxyBasicAuthHeader(
 1827           String                    userid,
 1828           String                    password,
 1829           Map<String, List<String>> headers
 1830       ) {
 1831           String userpass = userid;
 1832   
 1833           userpass += ":";
 1834           if (password != null) {
 1835               userpass += password;
 1836           }
 1837           String token = Base64Utility.encode(userpass.getBytes());
 1838           headers.put("Proxy-Authorization",
 1839                       createMutableList("Basic " + token));
 1840       }
 1841       
 1842       /**
 1843        * Wrapper output stream responsible for flushing headers and handling
 1844        * the incoming HTTP-level response (not necessarily the MEP response).
 1845        */
 1846       protected class WrappedOutputStream extends AbstractThresholdOutputStream {
 1847           /**
 1848            * This field contains the currently active connection.
 1849            */
 1850           protected HttpURLConnection connection;
 1851           
 1852           /**
 1853            * This boolean is true if the request must be cached.
 1854            */
 1855           protected boolean cachingForRetransmission;
 1856           
 1857           /**
 1858            * If we are going to be chunking, we won't flush till close which causes
 1859            * new chunks, small network packets, etc..
 1860            */
 1861           protected final boolean chunking;
 1862           
 1863           /**
 1864            * This field contains the output stream with which we cache
 1865            * the request. It maybe null if we are not caching.
 1866            */
 1867           protected CacheAndWriteOutputStream cachedStream;
 1868   
 1869           protected Message outMessage;
 1870           
 1871           protected WrappedOutputStream(
 1872                   Message m, 
 1873                   HttpURLConnection c, 
 1874                   boolean possibleRetransmit,
 1875                   boolean isChunking,
 1876                   int chunkThreshold
 1877           ) {
 1878               super(chunkThreshold);
 1879               this.outMessage = m;
 1880               connection = c;
 1881               cachingForRetransmission = possibleRetransmit;
 1882               chunking = isChunking;
 1883           }
 1884           
 1885           
 1886           @Override
 1887           public void thresholdNotReached() {
 1888               if (chunking) {
 1889                   connection.setFixedLengthStreamingMode(buffer.size());
 1890               }
 1891           }
 1892   
 1893           @Override
 1894           public void thresholdReached() {
 1895               if (chunking) {
 1896                   connection.setChunkedStreamingMode(-1);
 1897               }
 1898           }
 1899   
 1900           /**
 1901            * Perform any actions required on stream flush (freeze headers,
 1902            * reset output stream ... etc.)
 1903            */
 1904           @Override
 1905           protected void onFirstWrite() throws IOException {
 1906               try {
 1907                   handleHeadersTrustCaching();
 1908               } catch (IOException e) {
 1909                   if (e.getMessage() != null && e.getMessage().contains("HTTPS hostname wrong:")) {
 1910                       throw new IOException("The https URL hostname does not match the " 
 1911                           + "Common Name (CN) on the server certificate.  To disable this check " 
 1912                           + "(NOT recommended for production) set the CXF client TLS configuration " 
 1913                           + "property \"disableCNCheck\" to true.");
 1914                   } else {
 1915                       throw e;
 1916                   }
 1917               }
 1918           }
 1919           
 1920           protected void handleHeadersTrustCaching() throws IOException {
 1921               // Need to set the headers before the trust decision
 1922               // because they are set before the connect().
 1923               setURLRequestHeaders(outMessage);
 1924               
 1925               //
 1926               // This point is where the trust decision is made because the
 1927               // Sun implementation of URLConnection will not let us 
 1928               // set/addRequestProperty after a connect() call, and 
 1929               // makeTrustDecision needs to make a connect() call to
 1930               // make sure the proper information is available.
 1931               // 
 1932               makeTrustDecision(outMessage, connection);
 1933               
 1934               // Trust is okay, set up for writing the request.
 1935               
 1936               // If this is a GET method we must not touch the output
 1937               // stream as this automatically turns the request into a POST.
 1938               // Nor it should be done in case of DELETE/HEAD/OPTIONS 
 1939               // - strangely, empty PUTs work ok 
 1940               if (!"POST".equals(connection.getRequestMethod())
 1941                   && !"PUT".equals(connection.getRequestMethod())) {
 1942                   return;
 1943               }
 1944               if (outMessage.get("org.apache.cxf.post.empty") != null) {
 1945                   return;
 1946               }
 1947               
 1948               // If we need to cache for retransmission, store data in a
 1949               // CacheAndWriteOutputStream. Otherwise write directly to the output stream.
 1950               if (cachingForRetransmission) {
 1951                   cachedStream =
 1952                       new CacheAndWriteOutputStream(connection.getOutputStream());
 1953                   wrappedStream = cachedStream;
 1954               } else {
 1955                   wrappedStream = connection.getOutputStream();
 1956               }
 1957           }
 1958   
 1959           public void flush() throws IOException {
 1960               if (!chunking) {
 1961                   super.flush();
 1962               }
 1963           }
 1964           
 1965           /**
 1966            * Perform any actions required on stream closure (handle response etc.)
 1967            */
 1968           public void close() throws IOException {
 1969               try {
 1970                   if (buffer != null && buffer.size() > 0) {
 1971                       thresholdNotReached();
 1972                       LoadingByteArrayOutputStream tmp = buffer;
 1973                       buffer = null;
 1974                       super.write(tmp.getRawBytes(), 0, tmp.size());
 1975                   }
 1976                   if (!written) {
 1977                       handleHeadersTrustCaching();
 1978                   }
 1979                   if (!cachingForRetransmission) {
 1980                       super.close();
 1981                   } else if (cachedStream != null) {
 1982                       super.flush();
 1983                       cachedStream.getOut().close();
 1984                       cachedStream.closeFlowthroughStream();
 1985                   }
 1986   
 1987                   try {
 1988                       handleResponse();
 1989                   } finally {
 1990                       if (cachingForRetransmission && cachedStream != null) {
 1991                           cachedStream.close();
 1992                       }
 1993                   }
 1994               } catch (HttpRetryException e) {
 1995                   String msg = "HTTP response '" + e.responseCode() + ": "
 1996                                + connection.getResponseMessage() + "' invoking " + connection.getURL();
 1997                   switch (e.responseCode()) {
 1998                   case HttpURLConnection.HTTP_MOVED_PERM: // 301
 1999                   case HttpURLConnection.HTTP_MOVED_TEMP: // 302
 2000                       msg += " that returned location header '" + e.getLocation() + "'";
 2001                       break;
 2002                   case HttpURLConnection.HTTP_UNAUTHORIZED: // 401
 2003                       if (authorizationPolicy == null || authorizationPolicy.getUserName() == null) {
 2004                           msg += " with NO authorization username configured in conduit " + getConduitName();
 2005                       } else {
 2006                           msg += " with authorization username '" + authorizationPolicy.getUserName() + "'";
 2007                       }
 2008                       break;
 2009                   case HttpURLConnection.HTTP_PROXY_AUTH: // 407
 2010                       if (proxyAuthorizationPolicy == null || proxyAuthorizationPolicy.getUserName() == null) {
 2011                           msg += " with NO proxy authorization configured in conduit " + getConduitName();
 2012                       } else {
 2013                           msg += " with proxy authorization username '"
 2014                                  + proxyAuthorizationPolicy.getUserName() + "'";
 2015                       }
 2016                       if (clientSidePolicy == null || clientSidePolicy.getProxyServer() == null) {
 2017                           if (connection.usingProxy()) {
 2018                               msg += " using a proxy even if NONE is configured in CXF conduit "
 2019                                      + getConduitName()
 2020                                      + " (maybe one is configured by java.net.ProxySelector)";
 2021                           } else {
 2022                               msg += " but NO proxy was used by the connection (none configured in cxf "
 2023                                      + "conduit and none selected by java.net.ProxySelector)";
 2024                           }
 2025                       } else {
 2026                           msg += " using " + clientSidePolicy.getProxyServerType() + " proxy "
 2027                                  + clientSidePolicy.getProxyServer() + ":"
 2028                                  + clientSidePolicy.getProxyServerPort();
 2029                       }
 2030                       break;
 2031                   default:
 2032                       // No other type of HttpRetryException should be thrown
 2033                       break;
 2034                   }
 2035                   // pass cause with initCause() instead of constructor for jdk 1.5 compatibility
 2036                   throw (IOException) new IOException(msg).initCause(e);
 2037               } catch (IOException e) {
 2038                   String url = connection.getURL().toString();
 2039                   String origMessage = e.getMessage();
 2040                   if (origMessage != null && origMessage.contains(url)) {
 2041                       throw e;
 2042                   }
 2043                   throw mapException(e.getClass().getSimpleName() 
 2044                                      + " invoking " + connection.getURL() + ": "
 2045                                      + e.getMessage(), e,
 2046                                      IOException.class);
 2047               } catch (RuntimeException e) {
 2048                   throw mapException(e.getClass().getSimpleName() 
 2049                                      + " invoking " + connection.getURL() + ": "
 2050                                      + e.getMessage(), e,
 2051                                      RuntimeException.class);
 2052               }
 2053           }
 2054           private <T extends Exception> T mapException(String msg, 
 2055                                                        T ex, Class<T> cls) {
 2056               T ex2 = ex;
 2057               try {
 2058                   ex2 = cls.cast(ex.getClass().getConstructor(String.class).newInstance(msg));
 2059                   ex2.initCause(ex);
 2060               } catch (Throwable e) {
 2061                   ex2 = ex;
 2062               }
 2063               
 2064               
 2065               return ex2;
 2066           }
 2067           
 2068           /**
 2069            * This procedure handles all retransmits, if any.
 2070            *
 2071            * @throws IOException
 2072            */
 2073           protected void handleRetransmits() throws IOException {
 2074               // If we have a cachedStream, we are caching the request.
 2075               if (cachedStream != null) {
 2076   
 2077                   if (LOG.isLoggable(Level.FINE)) {
 2078                       LOG.fine("Conduit \""
 2079                                + getConduitName() 
 2080                                + "\" Transmit cached message to: " 
 2081                                + connection.getURL()
 2082                                + ": "
 2083                                + new String(cachedStream.getBytes()));
 2084                   }
 2085   
 2086                   HttpURLConnection oldcon = connection;
 2087                   
 2088                   HTTPClientPolicy policy = getClient(outMessage);
 2089                   
 2090                   // Default MaxRetransmits is -1 which means unlimited.
 2091                   int maxRetransmits = (policy == null)
 2092                                        ? -1
 2093                                        : policy.getMaxRetransmits();
 2094                   
 2095                   // MaxRetransmits of zero means zero.
 2096                   if (maxRetransmits == 0) {
 2097                       return;
 2098                   }
 2099                   
 2100                   int nretransmits = 0;
 2101                   
 2102                   connection = 
 2103                       processRetransmit(connection, outMessage, cachedStream);
 2104                   
 2105                   while (connection != oldcon) {
 2106                       nretransmits++;
 2107                       oldcon = connection;
 2108   
 2109                       // A negative max means unlimited.
 2110                       if (maxRetransmits < 0 || nretransmits < maxRetransmits) {
 2111                           connection = 
 2112                               processRetransmit(
 2113                                       connection, outMessage, cachedStream);
 2114                       }
 2115                   }
 2116               }
 2117           }
 2118           
 2119           /**
 2120            * This procedure is called on the close of the output stream so
 2121            * we are ready to handle the response from the connection. 
 2122            * We may retransmit until we finally get a response.
 2123            * 
 2124            * @throws IOException
 2125            */
 2126           protected void handleResponse() throws IOException {
 2127               
 2128               // Process retransmits until we fall out.
 2129               handleRetransmits();
 2130               
 2131               if (outMessage == null 
 2132                   || outMessage.getExchange() == null
 2133                   || outMessage.getExchange().isSynchronous()) {
 2134                   handleResponseInternal();
 2135               } else {
 2136                   Runnable runnable = new Runnable() {
 2137                       public void run() {
 2138                           try {
 2139                               handleResponseInternal();
 2140                           } catch (Exception e) {
 2141                               ((PhaseInterceptorChain)outMessage.getInterceptorChain()).unwind(outMessage);
 2142                               outMessage.setContent(Exception.class, e);
 2143                               outMessage.getInterceptorChain().getFaultObserver().onMessage(outMessage);
 2144                           }
 2145                       }
 2146                   };
 2147                   WorkQueueManager mgr = outMessage.getExchange().get(Bus.class)
 2148                       .getExtension(WorkQueueManager.class);
 2149                   AutomaticWorkQueue queue = mgr.getNamedWorkQueue("http-conduit");
 2150                   if (queue == null) {
 2151                       queue = mgr.getAutomaticWorkQueue();
 2152                   }
 2153                   queue.execute(runnable);
 2154               }
 2155           }
 2156           protected void handleResponseInternal() throws IOException {
 2157               Exchange exchange = outMessage.getExchange();
 2158               int responseCode = connection.getResponseCode();
 2159               if (outMessage != null && exchange != null) {
 2160                   exchange.put(Message.RESPONSE_CODE, responseCode);
 2161               }
 2162               
 2163               if (LOG.isLoggable(Level.FINE)) {
 2164                   LOG.fine("Response Code: " 
 2165                           + responseCode
 2166                           + " Conduit: " + getConduitName());
 2167                   LOG.fine("Content length: " + connection.getContentLength());
 2168                   Map<String, List<String>> headerFields = connection.getHeaderFields();
 2169                   if (null != headerFields) {
 2170                       StringBuilder buf = new StringBuilder();
 2171                       buf.append("Header fields: ");
 2172                       buf.append(System.getProperty("line.separator"));
 2173                       for (String h : headerFields.keySet()) {
 2174                           buf.append("    ");
 2175                           buf.append(h);
 2176                           buf.append(": ");
 2177                           buf.append(headerFields.get(h));
 2178                           buf.append(System.getProperty("line.separator"));
 2179                       }
 2180                       LOG.fine(buf.toString());
 2181                   }
 2182               }
 2183               
 2184               if (responseCode == HttpURLConnection.HTTP_NOT_FOUND
 2185                   && !MessageUtils.isTrue(outMessage.getContextualProperty(
 2186                       "org.apache.cxf.http.no_io_exceptions"))) {
 2187                   throw new IOException("HTTP response '" + responseCode + ": " 
 2188                       + connection.getResponseMessage() + "'");
 2189               }
 2190   
 2191               
 2192   
 2193               InputStream in = null;
 2194               if (isOneway(exchange) || isDecoupled()) {
 2195                   in = getPartialResponse(connection, responseCode);
 2196                   if (in == null) {
 2197                       // oneway operation or decoupled MEP without 
 2198                       // partial response
 2199                       connection.getInputStream().close();
 2200                       return;
 2201                   }
 2202               } else {
 2203                   //not going to be resending or anything, clear out the stuff in the out message
 2204                   //to free memory
 2205                   outMessage.removeContent(OutputStream.class);
 2206                   if (cachingForRetransmission && cachedStream != null) {
 2207                       cachedStream.close();
 2208                   }
 2209                   cachedStream = null;
 2210               }
 2211               
 2212               Message inMessage = new MessageImpl();
 2213               inMessage.setExchange(exchange);
 2214               Map<String, List<String>> origHeaders = connection.getHeaderFields();
 2215               Map<String, List<String>> headers = 
 2216                   new HashMap<String, List<String>>();
 2217               for (String key : connection.getHeaderFields().keySet()) {
 2218                   if (key != null) {
 2219                       headers.put(HttpHeaderHelper.getHeaderKey(key), 
 2220                           origHeaders.get(key));
 2221                   }
 2222               }
 2223               
 2224               inMessage.put(Message.PROTOCOL_HEADERS, headers);
 2225               inMessage.put(Message.RESPONSE_CODE, responseCode);
 2226               String ct = connection.getContentType();
 2227               inMessage.put(Message.CONTENT_TYPE, ct);
 2228               String charset = HttpHeaderHelper.findCharset(ct);
 2229               String normalizedEncoding = HttpHeaderHelper.mapCharset(charset);
 2230               if (normalizedEncoding == null) {
 2231                   String m = new org.apache.cxf.common.i18n.Message("INVALID_ENCODING_MSG",
 2232                                                                      LOG, charset).toString();
 2233                   LOG.log(Level.WARNING, m);
 2234                   throw new IOException(m);   
 2235               } 
 2236               inMessage.put(Message.ENCODING, normalizedEncoding);            
 2237                           
 2238               if (maintainSession) {
 2239                   List<String> cookies = connection.getHeaderFields().get("Set-Cookie");
 2240                   Cookie.handleSetCookie(sessionCookies, cookies);
 2241               }
 2242               if (responseCode != HttpURLConnection.HTTP_NOT_FOUND) {
 2243                   in = in == null
 2244                        ? connection.getErrorStream() == null
 2245                          ? connection.getInputStream()
 2246                          : connection.getErrorStream()
 2247                        : in;
 2248               }
 2249               // if (in == null) : it's perfectly ok for non-soap http services
 2250               // have no response body : those interceptors which do need it will check anyway        
 2251               inMessage.setContent(InputStream.class, in);
 2252               
 2253               
 2254               incomingObserver.onMessage(inMessage);
 2255               
 2256           }
 2257   
 2258       }
 2259       
 2260       /**
 2261        * Used to set appropriate message properties, exchange etc.
 2262        * as required for an incoming decoupled response (as opposed
 2263        * what's normally set by the Destination for an incoming
 2264        * request).
 2265        */
 2266       protected class InterposedMessageObserver implements MessageObserver {
 2267           /**
 2268            * Called for an incoming message.
 2269            * 
 2270            * @param inMessage
 2271            */
 2272           public void onMessage(Message inMessage) {
 2273               // disposable exchange, swapped with real Exchange on correlation
 2274               inMessage.setExchange(new ExchangeImpl());
 2275               inMessage.getExchange().put(Bus.class, bus);
 2276               inMessage.put(DECOUPLED_CHANNEL_MESSAGE, Boolean.TRUE);
 2277               // REVISIT: how to get response headers?
 2278               //inMessage.put(Message.PROTOCOL_HEADERS, req.getXXX());
 2279               getSetProtocolHeaders(inMessage);
 2280               inMessage.put(Message.RESPONSE_CODE, HttpURLConnection.HTTP_OK);
 2281   
 2282               // remove server-specific properties
 2283               inMessage.remove(AbstractHTTPDestination.HTTP_REQUEST);
 2284               inMessage.remove(AbstractHTTPDestination.HTTP_RESPONSE);
 2285               inMessage.remove(Message.ASYNC_POST_RESPONSE_DISPATCH);
 2286   
 2287               //cache this inputstream since it's defer to use in case of async
 2288               try {
 2289                   InputStream in = inMessage.getContent(InputStream.class);
 2290                   if (in != null) {
 2291                       CachedOutputStream cos = new CachedOutputStream();
 2292                       IOUtils.copy(in, cos);
 2293                       inMessage.setContent(InputStream.class, cos.getInputStream());
 2294                   }
 2295                   incomingObserver.onMessage(inMessage);
 2296               } catch (IOException e) {
 2297                   e.printStackTrace();
 2298               }
 2299           }
 2300       }
 2301       
 2302       public void assertMessage(Message message) {
 2303           PolicyUtils.assertClientPolicy(message, clientSidePolicy);
 2304       }
 2305       
 2306       public boolean canAssert(QName type) {
 2307           return PolicyUtils.HTTPCLIENTPOLICY_ASSERTION_QNAME.equals(type);  
 2308       }
 2309   
 2310       @Deprecated
 2311       public void setBasicAuthSupplier(HttpBasicAuthSupplier basicAuthSupplier) {
 2312           setAuthSupplier(basicAuthSupplier);
 2313       }
 2314       
 2315   }

Save This Page
Home » apache-cxf-2.2.7-src » org.apache » cxf » transport » http » [javadoc | source]