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