View Javadoc

    1   // ========================================================================
    2   // Copyright 2006-2007 Mort Bay Consulting Pty. Ltd.
    3   // ------------------------------------------------------------------------
    4   // Licensed under the Apache License, Version 2.0 (the "License");
    5   // you may not use this file except in compliance with the License.
    6   // You may obtain a copy of the License at
    7   // http://www.apache.org/licenses/LICENSE-2.0
    8   // Unless required by applicable law or agreed to in writing, software
    9   // distributed under the License is distributed on an "AS IS" BASIS,
    10  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    11  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  // ========================================================================
    14
    15  package org.mortbay.jetty.client;
    16
    17  import java.io.IOException;
    18  import java.io.InputStream;
    19  import java.net.UnknownHostException;
    20  import java.security.KeyStore;
    21  import java.security.SecureRandom;
    22  import java.util.Enumeration;
    23  import java.util.HashMap;
    24  import java.util.LinkedList;
    25  import java.util.Map;
    26  import java.util.Set;
    27
    28  import javax.net.ssl.HostnameVerifier;
    29  import javax.net.ssl.KeyManager;
    30  import javax.net.ssl.KeyManagerFactory;
    31  import javax.net.ssl.SSLContext;
    32  import javax.net.ssl.SSLSession;
    33  import javax.net.ssl.TrustManager;
    34  import javax.net.ssl.TrustManagerFactory;
    35  import javax.net.ssl.X509TrustManager;
    36
    37  import org.mortbay.component.LifeCycle;
    38  import org.mortbay.io.Buffer;
    39  import org.mortbay.io.ByteArrayBuffer;
    40  import org.mortbay.io.nio.DirectNIOBuffer;
    41  import org.mortbay.io.nio.IndirectNIOBuffer;
    42  import org.mortbay.jetty.AbstractBuffers;
    43  import org.mortbay.jetty.HttpSchemes;
    44  import org.mortbay.jetty.client.security.Authorization;
    45  import org.mortbay.jetty.client.security.RealmResolver;
    46  import org.mortbay.log.Log;
    47  import org.mortbay.resource.Resource;
    48  import org.mortbay.thread.QueuedThreadPool;
    49  import org.mortbay.thread.ThreadPool;
    50  import org.mortbay.thread.Timeout;
    51  import org.mortbay.util.Attributes;
    52  import org.mortbay.util.AttributesMap;
    53
    54  /**
    55   * Http Client.
    56   * <p/>
    57   * HttpClient is the main active component of the client API implementation.
    58   * It is the opposite of the Connectors in standard Jetty, in that it listens
    59   * for responses rather than requests.   Just like the connectors, there is a
    60   * blocking socket version and a non-blocking NIO version (implemented as nested classes
    61   * selected by {@link #setConnectorType(int)}).
    62   * <p/>
    63   * The an instance of {@link HttpExchange} is passed to the {@link #send(HttpExchange)} method
    64   * to send a request.  The exchange contains both the headers and content (source) of the request
    65   * plus the callbacks to handle responses.   A HttpClient can have many exchanges outstanding
    66   * and they may be queued on the {@link HttpDestination} waiting for a {@link HttpConnection},
    67   * queued in the {@link HttpConnection} waiting to be transmitted or pipelined on the actual
    68   * TCP/IP connection waiting for a response.
    69   * <p/>
    70   * The {@link HttpDestination} class is an aggregation of {@link HttpConnection}s for the
    71   * same host, port and protocol.   A destination may limit the number of connections
    72   * open and they provide a pool of open connections that may be reused.   Connections may also
    73   * be allocated from a destination, so that multiple request sources are not multiplexed
    74   * over the same connection.
    75   *
    76   * @see {@link HttpExchange}
    77   * @see {@link HttpDestination}
    78   * @author Greg Wilkins
    79   * @author Matthew Purland
    80   * @author Guillaume Nodet
    81   */
    82  public class HttpClient extends AbstractBuffers implements Attributes
    83  {
    84      public static final int CONNECTOR_SOCKET=0;
    85      public static final int CONNECTOR_SELECT_CHANNEL=2;
    86
    87      private int _connectorType=CONNECTOR_SELECT_CHANNEL;
    88      private boolean _useDirectBuffers=true;
    89      private int _maxConnectionsPerAddress=32;
    90      private Map<Address, HttpDestination> _destinations = new HashMap<Address, HttpDestination>();
    91      ThreadPool _threadPool;
    92      Connector _connector;
    93      private long _idleTimeout=20000;
    94      private long _timeout=320000;
    95      private int _soTimeout = 10000;
    96      private Timeout _timeoutQ = new Timeout();
    97      private Timeout _idleTimeoutQ = new Timeout();
    98      private Address _proxy;
    99      private Authorization _proxyAuthentication;
    100     private Set<String> _noProxy;
    101     private int _maxRetries = 3;
    102     private LinkedList<String> _registeredListeners;
    103
    104     // TODO clean up and add getters/setters to some of this maybe
    105     private String _keyStoreLocation;
    106     private String _keyStoreType="JKS";
    107     private String _keyStorePassword;
    108     private String _keyManagerAlgorithm = "SunX509";
    109     private String _keyManagerPassword;
    110     private String _trustStoreLocation;
    111     private String _trustStoreType="JKS";
    112     private String _trustStorePassword;
    113     private String _trustManagerAlgorithm = "SunX509";
    114
    115     private SSLContext _sslContext;
    116
    117     private String _protocol="TLS";
    118     private String _provider;
    119     private String _secureRandomAlgorithm;
    120
    121     private RealmResolver _realmResolver;
    122
    123     private AttributesMap _attributes=new AttributesMap();
    124
    125     /* ------------------------------------------------------------------------------- */
    126     public void dump() throws IOException
    127     {
    128         for (Map.Entry<Address, HttpDestination> entry : _destinations.entrySet())
    129         {
    130             System.err.println("\n"+entry.getKey()+":");
    131             entry.getValue().dump();
    132         }
    133     }
    134
    135     /* ------------------------------------------------------------------------------- */
    136     public void send(HttpExchange exchange) throws IOException
    137     {
    138         if (!isStarted())
    139             throw new IllegalStateException("!started");
    140         boolean ssl=HttpSchemes.HTTPS_BUFFER.equalsIgnoreCase(exchange.getScheme());
    141         exchange.setStatus(HttpExchange.STATUS_WAITING_FOR_CONNECTION);
    142         HttpDestination destination=getDestination(exchange.getAddress(),ssl);
    143         destination.send(exchange);
    144     }
    145
    146     /* ------------------------------------------------------------ */
    147     /**
    148      * @return the threadPool
    149      */
    150     public ThreadPool getThreadPool()
    151     {
    152         return _threadPool;
    153     }
    154
    155     /* ------------------------------------------------------------ */
    156     /**
    157      * @param threadPool the threadPool to set
    158      */
    159     public void setThreadPool(ThreadPool threadPool)
    160     {
    161         _threadPool=threadPool;
    162     }
    163
    164
    165     /* ------------------------------------------------------------ */
    166     /**
    167      * @param name
    168      * @return Attribute associated with client
    169      */
    170     public Object getAttribute(String name)
    171     {
    172         return _attributes.getAttribute(name);
    173     }
    174
    175     /* ------------------------------------------------------------ */
    176     /**
    177      * @return names of attributes associated with client
    178      */
    179     public Enumeration getAttributeNames()
    180     {
    181         return _attributes.getAttributeNames();
    182     }
    183
    184     /* ------------------------------------------------------------ */
    185     /**
    186      * @param name
    187      */
    188     public void removeAttribute(String name)
    189     {
    190         _attributes.removeAttribute(name);
    191     }
    192
    193     /* ------------------------------------------------------------ */
    194     /**
    195      * Set an attribute on the HttpClient.
    196      * Attributes are not used by the client, but are provided for
    197      * so that users of a shared HttpClient may share other structures.
    198      * @param name
    199      * @param attribute
    200      */
    201     public void setAttribute(String name, Object attribute)
    202     {
    203         _attributes.setAttribute(name,attribute);
    204     }
    205
    206     /* ------------------------------------------------------------ */
    207     /**
    208      * @param name
    209      * @return
    210      */
    211     public void clearAttributes()
    212     {
    213         _attributes.clearAttributes();
    214     }
    215
    216     /* ------------------------------------------------------------------------------- */
    217     public HttpDestination getDestination(Address remote, boolean ssl) throws UnknownHostException, IOException
    218     {
    219         if (remote==null)
    220             throw new UnknownHostException("Remote socket address cannot be null.");
    221
    222         synchronized (_destinations)
    223         {
    224             HttpDestination destination=_destinations.get(remote);
    225             if (destination==null)
    226             {
    227                 destination=new HttpDestination(this,remote,ssl,_maxConnectionsPerAddress);
    228                 if (_proxy != null && (_noProxy == null || !_noProxy.contains(remote.getHost())))
    229                 {
    230                     destination.setProxy(_proxy);
    231                     if (_proxyAuthentication!=null)
    232                         destination.setProxyAuthentication(_proxyAuthentication);
    233                 }
    234                 _destinations.put(remote,destination);
    235             }
    236             return destination;
    237         }
    238     }
    239
    240     /* ------------------------------------------------------------ */
    241     public void schedule(Timeout.Task task)
    242     {
    243         _timeoutQ.schedule(task);
    244     }
    245
    246     /* ------------------------------------------------------------ */
    247     public void scheduleIdle(Timeout.Task task)
    248     {
    249         _idleTimeoutQ.schedule(task);
    250     }
    251
    252     /* ------------------------------------------------------------ */
    253     public void cancel(Timeout.Task task)
    254     {
    255         task.cancel();
    256     }
    257
    258     /* ------------------------------------------------------------ */
    259     /**
    260      * Get whether the connector can use direct NIO buffers.
    261      */
    262     public boolean getUseDirectBuffers()
    263     {
    264         return _useDirectBuffers;
    265     }
    266
    267     /* ------------------------------------------------------------ */
    268     public void setRealmResolver( RealmResolver resolver )
    269     {
    270         _realmResolver = resolver;
    271     }
    272
    273     /* ------------------------------------------------------------ */
    274     /**
    275      * returns the SecurityRealmResolver registered with the HttpClient or null
    276      *
    277      * @return
    278      */
    279     public RealmResolver getRealmResolver()
    280     {
    281         return _realmResolver;
    282     }
    283
    284     /* ------------------------------------------------------------ */
    285     public boolean hasRealms()
    286     {
    287         return _realmResolver==null?false:true;
    288     }
    289
    290
    291     /**
    292      * Registers a listener that can listen to the stream of execution between the client and the
    293      * server and influence events.  Sequential calls to the method wrapper sequentially wrap the preceeding
    294      * listener in a delegation model.
    295      * <p/>
    296      * NOTE: the SecurityListener is a special listener which doesn't need to be added via this
    297      * mechanic, if you register security realms then it will automatically be added as the top listener of the
    298      * delegation stack.
    299      *
    300      * @param listenerClass
    301      */
    302     public void registerListener( String listenerClass )
    303     {
    304         if ( _registeredListeners == null )
    305         {
    306             _registeredListeners = new LinkedList<String>();
    307         }
    308         _registeredListeners.add( listenerClass );
    309     }
    310
    311     public LinkedList<String> getRegisteredListeners()
    312     {
    313         return _registeredListeners;
    314     }
    315
    316
    317     /* ------------------------------------------------------------ */
    318     /**
    319      * Set to use NIO direct buffers.
    320      *
    321      * @param direct
    322      *            If True (the default), the connector can use NIO direct
    323      *            buffers. Some JVMs have memory management issues (bugs) with
    324      *            direct buffers.
    325      */
    326     public void setUseDirectBuffers(boolean direct)
    327     {
    328         _useDirectBuffers=direct;
    329     }
    330
    331     /* ------------------------------------------------------------ */
    332     /**
    333      * Get the type of connector (socket, blocking or select) in use.
    334      */
    335     public int getConnectorType()
    336     {
    337         return _connectorType;
    338     }
    339
    340     /* ------------------------------------------------------------ */
    341     public void setConnectorType(int connectorType)
    342     {
    343         this._connectorType=connectorType;
    344     }
    345
    346     /* ------------------------------------------------------------ */
    347     /**
    348      * Create a new NIO buffer. If using direct buffers, it will create a direct
    349      * NIO buffer, other than an indirect buffer.
    350      */
    351     @Override
    352     protected Buffer newBuffer(int size)
    353     {
    354         if (_connectorType!=CONNECTOR_SOCKET)
    355         {
    356             Buffer buf=null;
    357             if (size==getHeaderBufferSize())
    358                 buf=new IndirectNIOBuffer(size);
    359             else if (_useDirectBuffers)
    360                 buf=new DirectNIOBuffer(size);
    361             else
    362                 buf=new IndirectNIOBuffer(size);
    363             return buf;
    364         }
    365         else
    366         {
    367             return new ByteArrayBuffer(size);
    368         }
    369     }
    370
    371     /* ------------------------------------------------------------ */
    372     public int getMaxConnectionsPerAddress()
    373     {
    374         return _maxConnectionsPerAddress;
    375     }
    376
    377     /* ------------------------------------------------------------ */
    378     public void setMaxConnectionsPerAddress(int maxConnectionsPerAddress)
    379     {
    380         _maxConnectionsPerAddress=maxConnectionsPerAddress;
    381     }
    382
    383     /* ------------------------------------------------------------ */
    384     protected void doStart() throws Exception
    385     {
    386         super.doStart();
    387
    388         _timeoutQ.setDuration(_timeout);
    389         _timeoutQ.setNow();
    390         _idleTimeoutQ.setDuration(_idleTimeout);
    391         _idleTimeoutQ.setNow();
    392
    393         if(_threadPool==null)
    394         {
    395             QueuedThreadPool pool = new QueuedThreadPool();
    396             pool.setMaxThreads(16);
    397             pool.setDaemon(true);
    398             pool.setName("HttpClient");
    399             _threadPool=pool;
    400         }
    401
    402         if (_threadPool instanceof LifeCycle)
    403         {
    404             ((LifeCycle)_threadPool).start();
    405         }
    406
    407
    408         if (_connectorType==CONNECTOR_SELECT_CHANNEL)
    409         {
    410
    411             _connector=new SelectConnector(this);
    412         }
    413         else
    414         {
    415             _connector=new SocketConnector(this);
    416         }
    417         _connector.start();
    418
    419         _threadPool.dispatch(new Runnable()
    420         {
    421             public void run()
    422             {
    423                 while (isRunning())
    424                 {
    425                     _timeoutQ.tick(System.currentTimeMillis());
    426                     _idleTimeoutQ.tick(_timeoutQ.getNow());
    427                     try
    428                     {
    429                         Thread.sleep(200);
    430                     }
    431                     catch (InterruptedException e)
    432                     {
    433                         Log.ignore(e);
    434                     }
    435                 }
    436             }
    437         });
    438
    439     }
    440
    441     /* ------------------------------------------------------------ */
    442     long getNow()
    443     {
    444         return _timeoutQ.getNow();
    445     }
    446
    447     /* ------------------------------------------------------------ */
    448     protected void doStop() throws Exception
    449     {
    450         _connector.stop();
    451         _connector=null;
    452         if (_threadPool instanceof LifeCycle)
    453         {
    454             ((LifeCycle)_threadPool).stop();
    455         }
    456         for (HttpDestination destination : _destinations.values())
    457         {
    458             destination.close();
    459         }
    460
    461         _timeoutQ.cancelAll();
    462         _idleTimeoutQ.cancelAll();
    463         super.doStop();
    464     }
    465
    466     /* ------------------------------------------------------------ */
    467     interface Connector extends LifeCycle
    468     {
    469         public void startConnection(HttpDestination destination) throws IOException;
    470
    471     }
    472
    473     /**
    474      * if a keystore location has been provided then client will attempt to use it as the keystore,
    475      * otherwise we simply ignore certificates and run with a loose ssl context.
    476      *
    477      * @return
    478      * @throws IOException
    479      */
    480     protected SSLContext getSSLContext() throws IOException
    481     {
    482    	if (_sslContext == null)
    483     	{
    484             if (_keyStoreLocation == null)
    485             {
    486                 _sslContext = getLooseSSLContext();
    487             }
    488             else
    489             {
    490                 _sslContext = getStrictSSLContext();
    491             }
    492         }
    493     	return _sslContext;
    494     }
    495
    496     protected SSLContext getStrictSSLContext() throws IOException
    497     {
    498
    499         try
    500         {
    501             if (_trustStoreLocation==null)
    502             {
    503                 _trustStoreLocation=_keyStoreLocation;
    504                 _trustStoreType=_keyStoreType;
    505             }
    506
    507             KeyManager[] keyManagers=null;
    508             InputStream keystoreInputStream = null;
    509
    510             keystoreInputStream= Resource.newResource(_keyStoreLocation).getInputStream();
    511             KeyStore keyStore=KeyStore.getInstance(_keyStoreType);
    512             keyStore.load(keystoreInputStream,_keyStorePassword==null?null:_keyStorePassword.toString().toCharArray());
    513
    514             KeyManagerFactory keyManagerFactory=KeyManagerFactory.getInstance(_keyManagerAlgorithm);
    515             keyManagerFactory.init(keyStore,_keyManagerPassword==null?null:_keyManagerPassword.toString().toCharArray());
    516             keyManagers=keyManagerFactory.getKeyManagers();
    517
    518             TrustManager[] trustManagers=null;
    519             InputStream truststoreInputStream = null;
    520
    521                 truststoreInputStream = Resource.newResource(_trustStoreLocation).getInputStream();
    522             KeyStore trustStore=KeyStore.getInstance(_trustStoreType);
    523             trustStore.load(truststoreInputStream,_trustStorePassword==null?null:_trustStorePassword.toString().toCharArray());
    524
    525             TrustManagerFactory trustManagerFactory=TrustManagerFactory.getInstance(_trustManagerAlgorithm);
    526             trustManagerFactory.init(trustStore);
    527             trustManagers=trustManagerFactory.getTrustManagers();
    528
    529             SecureRandom secureRandom=_secureRandomAlgorithm==null?null:SecureRandom.getInstance(_secureRandomAlgorithm);
    530             SSLContext context=_provider==null?SSLContext.getInstance(_protocol):SSLContext.getInstance(_protocol,_provider);
    531             context.init(keyManagers,trustManagers,secureRandom);
    532             return context;
    533         }
    534         catch ( Exception e )
    535         {
    536             Log.debug(e);
    537             throw new IOException( "error generating ssl context for " + _keyStoreLocation  + " " + e.getMessage() );
    538         }
    539     }
    540
    541     protected SSLContext getLooseSSLContext() throws IOException
    542     {
    543
    544         // Create a trust manager that does not validate certificate
    545         // chains
    546         TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager()
    547         {
    548             public java.security.cert.X509Certificate[] getAcceptedIssuers()
    549             {
    550                 return null;
    551             }
    552
    553             public void checkClientTrusted( java.security.cert.X509Certificate[] certs, String authType )
    554             {
    555             }
    556
    557             public void checkServerTrusted( java.security.cert.X509Certificate[] certs, String authType )
    558             {
    559             }
    560         } };
    561
    562         HostnameVerifier hostnameVerifier = new HostnameVerifier()
    563         {
    564             public boolean verify( String urlHostName, SSLSession session )
    565             {
    566                 Log.warn( "Warning: URL Host: " + urlHostName + " vs." + session.getPeerHost() );
    567                 return true;
    568             }
    569         };
    570
    571         // Install the all-trusting trust manager
    572         try
    573         {
    574             // TODO real trust manager
    575             SSLContext sslContext = SSLContext.getInstance( "SSL" );
    576             sslContext.init( null, trustAllCerts, new java.security.SecureRandom() );
    577             return sslContext;
    578         }
    579         catch ( Exception e )
    580         {
    581             Log.debug(e);
    582             throw new IOException( "issue ignoring certs" );
    583         }
    584     }
    585
    586     /* ------------------------------------------------------------ */
    587     /**
    588      * @return the period in milliseconds a {@link HttpConnection} can be idle for before it is closed.
    589      */
    590     public long getIdleTimeout()
    591     {
    592         return _idleTimeout;
    593     }
    594
    595     /* ------------------------------------------------------------ */
    596     /**
    597      * @param ms the period in milliseconds a {@link HttpConnection} can be idle for before it is closed.
    598      */
    599     public void setIdleTimeout(long ms)
    600     {
    601         _idleTimeout=ms;
    602     }
    603
    604     /* ------------------------------------------------------------ */
    605     public int getSoTimeout()
    606     {
    607         return _soTimeout;
    608     }
    609
    610     /* ------------------------------------------------------------ */
    611     public void setSoTimeout(int so)
    612     {
    613         _soTimeout = so;
    614     }
    615
    616     /* ------------------------------------------------------------ */
    617     /**
    618      * @return the period in ms that an exchange will wait for a response from the server.
    619      */
    620     public long getTimeout()
    621     {
    622         return _timeout;
    623     }
    624
    625     /* ------------------------------------------------------------ */
    626     /**
    627      * @param ms the period in ms that an exchange will wait for a response from the server.
    628      */
    629     public void setTimeout(long ms)
    630     {
    631         _timeout=ms;
    632     }
    633
    634     /* ------------------------------------------------------------ */
    635     public Address getProxy()
    636     {
    637         return _proxy;
    638     }
    639
    640     /* ------------------------------------------------------------ */
    641     public void setProxy(Address proxy)
    642     {
    643         this._proxy = proxy;
    644     }
    645
    646     /* ------------------------------------------------------------ */
    647     public Authorization getProxyAuthentication()
    648     {
    649         return _proxyAuthentication;
    650     }
    651
    652     /* ------------------------------------------------------------ */
    653     public void setProxyAuthentication(Authorization authentication)
    654     {
    655         _proxyAuthentication = authentication;
    656     }
    657
    658     /* ------------------------------------------------------------ */
    659     public boolean isProxied()
    660     {
    661         return this._proxy!=null;
    662     }
    663
    664     /* ------------------------------------------------------------ */
    665     public Set<String> getNoProxy()
    666     {
    667         return _noProxy;
    668     }
    669
    670     /* ------------------------------------------------------------ */
    671     public void setNoProxy(Set<String> noProxyAddresses)
    672     {
    673         _noProxy = noProxyAddresses;
    674     }
    675
    676     /* ------------------------------------------------------------ */
    677     public int maxRetries()
    678     {
    679         return _maxRetries;
    680     }
    681
    682      /* ------------------------------------------------------------ */
    683     public void setMaxRetries( int retries )
    684     {
    685         _maxRetries = retries;
    686     }
    687
    688     /* ------------------------------------------------------------ */
    689     public String getTrustStoreLocation()
    690     {
    691         return _trustStoreLocation;
    692     }
    693
    694     /* ------------------------------------------------------------ */
    695     public void setTrustStoreLocation(String trustStoreLocation)
    696     {
    697         this._trustStoreLocation = trustStoreLocation;
    698     }
    699
    700     /* ------------------------------------------------------------ */
    701     public String getKeyStoreLocation()
    702     {
    703         return _keyStoreLocation;
    704     }
    705
    706     /* ------------------------------------------------------------ */
    707     public void setKeyStoreLocation(String keyStoreLocation)
    708     {
    709         this._keyStoreLocation = keyStoreLocation;
    710     }
    711
    712     /* ------------------------------------------------------------ */
    713     public void setKeyStorePassword(String _keyStorePassword)
    714     {
    715         this._keyStorePassword = _keyStorePassword;
    716     }
    717
    718     /* ------------------------------------------------------------ */
    719     public void setKeyManagerPassword(String _keyManagerPassword)
    720     {
    721         this._keyManagerPassword = _keyManagerPassword;
    722     }
    723
    724     /* ------------------------------------------------------------ */
    725     public void setTrustStorePassword(String _trustStorePassword)
    726     {
    727         this._trustStorePassword = _trustStorePassword;
    728     }
    729 }