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 }