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 
dump()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     /* ------------------------------------------------------------------------------- */
send(HttpExchange exchange)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      */
getThreadPool()142     public ThreadPool getThreadPool()
143     {
144         return _threadPool;
145     }
146 
147     /* ------------------------------------------------------------ */
148     /**
149      * @param threadPool the threadPool to set
150      */
setThreadPool(ThreadPool threadPool)151     public void setThreadPool(ThreadPool threadPool)
152     {
153         _threadPool=threadPool;
154     }
155 
156     /* ------------------------------------------------------------------------------- */
getDestination(Address remote, boolean ssl)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     /* ------------------------------------------------------------ */
schedule(Timeout.Task task)181     public void schedule(Timeout.Task task)
182     {
183         _timeoutQ.schedule(task);
184     }
185 
186     /* ------------------------------------------------------------ */
cancel(Timeout.Task task)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      */
getUseDirectBuffers()196     public boolean getUseDirectBuffers()
197     {
198         return _useDirectBuffers;
199     }
200 
201     /* ------------------------------------------------------------ */
setRealmResolver( RealmResolver resolver )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      */
getRealmResolver()213     public RealmResolver getRealmResolver()
214     {
215         return _realmResolver;
216     }
217 
218     /* ------------------------------------------------------------ */
hasRealms()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      */
registerListener( String listenerClass )236     public void registerListener( String listenerClass )
237     {
238         if ( _registeredListeners == null )
239         {
240             _registeredListeners = new LinkedList<String>();
241         }
242         _registeredListeners.add( listenerClass );
243     }
244 
getRegisteredListeners()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      */
setUseDirectBuffers(boolean direct)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      */
getConnectorType()269     public int getConnectorType()
270     {
271         return _connectorType;
272     }
273 
274     /* ------------------------------------------------------------ */
setConnectorType(int connectorType)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
newBuffer(int size)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     /* ------------------------------------------------------------ */
getMaxConnectionsPerAddress()306     public int getMaxConnectionsPerAddress()
307     {
308         return _maxConnectionsPerAddress;
309     }
310 
311     /* ------------------------------------------------------------ */
setMaxConnectionsPerAddress(int maxConnectionsPerAddress)312     public void setMaxConnectionsPerAddress(int maxConnectionsPerAddress)
313     {
314         _maxConnectionsPerAddress=maxConnectionsPerAddress;
315     }
316 
317     /* ------------------------------------------------------------ */
doStart()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     /* ------------------------------------------------------------ */
doStop()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     {
startConnection(HttpDestination destination)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      */
getSSLContext()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 
getStrictSSLContext()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 
getLooseSSLContext()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      */
getIdleTimeout()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      */
setIdleTimeout(long ms)522     public void setIdleTimeout(long ms)
523     {
524         _idleTimeout=ms;
525     }
526 
527     /* ------------------------------------------------------------ */
getSoTimeout()528     public int getSoTimeout()
529     {
530         return _soTimeout;
531     }
532 
533     /* ------------------------------------------------------------ */
setSoTimeout(int so)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      */
getTimeout()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      */
setTimeout(long ms)552     public void setTimeout(long ms)
553     {
554         _timeout=ms;
555     }
556 
557     /* ------------------------------------------------------------ */
getProxy()558     public Address getProxy()
559     {
560         return _proxy;
561     }
562 
563     /* ------------------------------------------------------------ */
setProxy(Address proxy)564     public void setProxy(Address proxy)
565     {
566         this._proxy = proxy;
567     }
568 
569     /* ------------------------------------------------------------ */
getProxyAuthentication()570     public Authorization getProxyAuthentication()
571     {
572         return _proxyAuthentication;
573     }
574 
575     /* ------------------------------------------------------------ */
setProxyAuthentication(Authorization authentication)576     public void setProxyAuthentication(Authorization authentication)
577     {
578         _proxyAuthentication = authentication;
579     }
580 
581     /* ------------------------------------------------------------ */
isProxied()582     public boolean isProxied()
583     {
584         return this._proxy!=null;
585     }
586 
587     /* ------------------------------------------------------------ */
getNoProxy()588     public Set<String> getNoProxy()
589     {
590         return _noProxy;
591     }
592 
593     /* ------------------------------------------------------------ */
setNoProxy(Set<String> noProxyAddresses)594     public void setNoProxy(Set<String> noProxyAddresses)
595     {
596         _noProxy = noProxyAddresses;
597     }
598 
599     /* ------------------------------------------------------------ */
maxRetries()600     public int maxRetries()
601     {
602         return _maxRetries;
603     }
604 
605      /* ------------------------------------------------------------ */
setMaxRetries( int retries )606     public void setMaxRetries( int retries )
607     {
608         _maxRetries = retries;
609     }
610 
getTrustStoreLocation()611     public String getTrustStoreLocation()
612     {
613         return _trustStoreLocation;
614     }
615 
setTrustStoreLocation(String trustStoreLocation)616     public void setTrustStoreLocation(String trustStoreLocation)
617     {
618         this._trustStoreLocation = trustStoreLocation;
619     }
620 
getKeyStoreLocation()621     public String getKeyStoreLocation()
622     {
623         return _keyStoreLocation;
624     }
625 
setKeyStoreLocation(String keyStoreLocation)626     public void setKeyStoreLocation(String keyStoreLocation)
627     {
628         this._keyStoreLocation = keyStoreLocation;
629     }
630 
setKeyStorePassword(String _keyStorePassword)631     public void setKeyStorePassword(String _keyStorePassword)
632     {
633         this._keyStorePassword = _keyStorePassword;
634     }
635 
setKeyManagerPassword(String _keyManagerPassword)636     public void setKeyManagerPassword(String _keyManagerPassword)
637     {
638         this._keyManagerPassword = _keyManagerPassword;
639     }
640 
setTrustStorePassword(String _trustStorePassword)641     public void setTrustStorePassword(String _trustStorePassword)
642     {
643         this._trustStorePassword = _trustStorePassword;
644     }
645 }
646