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