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.jetty.AbstractBuffers;
42  import org.mortbay.jetty.HttpSchemes;
43  import org.mortbay.jetty.client.security.Authorization;
44  import org.mortbay.jetty.client.security.RealmResolver;
45  import org.mortbay.log.Log;
46  import org.mortbay.resource.Resource;
47  import org.mortbay.thread.QueuedThreadPool;
48  import org.mortbay.thread.ThreadPool;
49  import org.mortbay.thread.Timeout;
50  
51  /**
52   * Http Client.
53   * <p/>
54   * HttpClient is the main active component of the client API implementation.
55   * It is the opposite of the Connectors in standard Jetty, in that it listens
56   * for responses rather than requests.   Just like the connectors, there is a
57   * blocking socket version and a non-blocking NIO version (implemented as nested classes
58   * selected by {@link #setConnectorType(int)}).
59   * <p/>
60   * The an instance of {@link HttpExchange} is passed to the {@link #send(HttpExchange)} method
61   * to send a request.  The exchange contains both the headers and content (source) of the request
62   * plus the callbacks to handle responses.   A HttpClient can have many exchanges outstanding
63   * and they may be queued on the {@link HttpDestination} waiting for a {@link HttpConnection},
64   * queued in the {@link HttpConnection} waiting to be transmitted or pipelined on the actual
65   * TCP/IP connection waiting for a response.
66   * <p/>
67   * The {@link HttpDestination} class is an aggregation of {@link HttpConnection}s for the
68   * same host, port and protocol.   A destination may limit the number of connections
69   * open and they provide a pool of open connections that may be reused.   Connections may also
70   * be allocated from a destination, so that multiple request sources are not multiplexed
71   * over the same connection.
72   *
73   * @see {@link HttpExchange}
74   * @see {@link HttpDestination}
75   * @author Greg Wilkins
76   * @author Matthew Purland
77   * @author Guillaume Nodet
78   */
79  public class HttpClient extends AbstractBuffers
80  {
81      public static final int CONNECTOR_SOCKET=0;
82      public static final int CONNECTOR_SELECT_CHANNEL=2;
83  
84      private int _connectorType=CONNECTOR_SELECT_CHANNEL;
85      private boolean _useDirectBuffers=true;
86      private int _maxConnectionsPerAddress=32;
87      private Map<Address, HttpDestination> _destinations = new HashMap<Address, HttpDestination>();
88      ThreadPool _threadPool;
89      Connector _connector;
90      private long _idleTimeout=20000;
91      private long _timeout=320000;
92      private int _soTimeout = 10000;
93      private Timeout _timeoutQ = new Timeout();
94      private Address _proxy;
95      private Authorization _proxyAuthentication;
96      private Set<String> _noProxy;
97      private int _maxRetries = 3;
98      private LinkedList<String> _registeredListeners;
99  
100     // TODO clean up and add getters/setters to some of this maybe
101     private String _keyStoreLocation;
102     private String _keyStoreType="JKS";
103     private String _keyStorePassword;
104     private String _keyManagerAlgorithm = "SunX509";
105     private String _keyManagerPassword;
106     private String _trustStoreLocation;
107     private String _trustStoreType="JKS";
108     private String _trustStorePassword;
109     private String _trustManagerAlgorithm = "SunX509";
110 
111     private SSLContext _sslContext;
112 
113     private String _protocol="TLS";
114     private String _provider;
115     private String _secureRandomAlgorithm;
116 
117     private RealmResolver _realmResolver;
118 
119     public void dump() throws IOException
120     {
121         for (Map.Entry<Address, HttpDestination> entry : _destinations.entrySet())
122         {
123             System.err.println("\n"+entry.getKey()+":");
124             entry.getValue().dump();
125         }
126     }
127 
128     /* ------------------------------------------------------------------------------- */
129     public void send(HttpExchange exchange) throws IOException
130     {
131         boolean ssl=HttpSchemes.HTTPS_BUFFER.equalsIgnoreCase(exchange.getScheme());
132         exchange.setStatus(HttpExchange.STATUS_WAITING_FOR_CONNECTION);
133         HttpDestination destination=getDestination(exchange.getAddress(),ssl);
134         destination.send(exchange);
135     }
136 
137     /* ------------------------------------------------------------ */
138     /**
139      * @return the threadPool
140      */
141     public ThreadPool getThreadPool()
142     {
143         return _threadPool;
144     }
145 
146     /* ------------------------------------------------------------ */
147     /**
148      * @param threadPool the threadPool to set
149      */
150     public void setThreadPool(ThreadPool threadPool)
151     {
152         _threadPool=threadPool;
153     }
154 
155     /* ------------------------------------------------------------------------------- */
156     public HttpDestination getDestination(Address remote, boolean ssl) throws UnknownHostException, IOException
157     {
158         if (remote==null)
159             throw new UnknownHostException("Remote socket address cannot be null.");
160 
161         synchronized (_destinations)
162         {
163             HttpDestination destination=_destinations.get(remote);
164             if (destination==null)
165             {
166                 destination=new HttpDestination(this,remote,ssl,_maxConnectionsPerAddress);
167                 if (_proxy != null && (_noProxy == null || !_noProxy.contains(remote.getHost())))
168                 {
169                     destination.setProxy(_proxy);
170                     if (_proxyAuthentication!=null)
171                         destination.setProxyAuthentication(_proxyAuthentication);
172                 }
173                 _destinations.put(remote,destination);
174             }
175             return destination;
176         }
177     }
178 
179     /* ------------------------------------------------------------ */
180     public void schedule(Timeout.Task task)
181     {
182         _timeoutQ.schedule(task);
183     }
184 
185     /* ------------------------------------------------------------ */
186     public void cancel(Timeout.Task task)
187     {
188         task.cancel();
189     }
190 
191     /* ------------------------------------------------------------ */
192     /**
193      * Get whether the connector can use direct NIO buffers.
194      */
195     public boolean getUseDirectBuffers()
196     {
197         return _useDirectBuffers;
198     }
199 
200     /* ------------------------------------------------------------ */
201     public void setRealmResolver( RealmResolver resolver )
202     {
203         _realmResolver = resolver;
204     }
205 
206     /* ------------------------------------------------------------ */
207     /**
208      * returns the SecurityRealmResolver registered with the HttpClient or null
209      *
210      * @return
211      */
212     public RealmResolver getRealmResolver()
213     {
214         return _realmResolver;
215     }
216 
217     /* ------------------------------------------------------------ */
218     public boolean hasRealms()
219     {
220         return _realmResolver==null?false:true;
221     }
222 
223 
224     /**
225      * Registers a listener that can listen to the stream of execution between the client and the
226      * server and influence events.  Sequential calls to the method wrapper sequentially wrap the preceeding
227      * listener in a delegation model.
228      * <p/>
229      * NOTE: the SecurityListener is a special listener which doesn't need to be added via this
230      * mechanic, if you register security realms then it will automatically be added as the top listener of the
231      * delegation stack.
232      *
233      * @param listenerClass
234      */
235     public void registerListener( String listenerClass )
236     {
237         if ( _registeredListeners == null )
238         {
239             _registeredListeners = new LinkedList<String>();
240         }
241         _registeredListeners.add( listenerClass );
242     }
243 
244     public LinkedList<String> getRegisteredListeners()
245     {
246         return _registeredListeners;
247     }
248 
249 
250     /* ------------------------------------------------------------ */
251     /**
252      * Set to use NIO direct buffers.
253      *
254      * @param direct
255      *            If True (the default), the connector can use NIO direct
256      *            buffers. Some JVMs have memory management issues (bugs) with
257      *            direct buffers.
258      */
259     public void setUseDirectBuffers(boolean direct)
260     {
261         _useDirectBuffers=direct;
262     }
263 
264     /* ------------------------------------------------------------ */
265     /**
266      * Get the type of connector (socket, blocking or select) in use.
267      */
268     public int getConnectorType()
269     {
270         return _connectorType;
271     }
272 
273     /* ------------------------------------------------------------ */
274     public void setConnectorType(int connectorType)
275     {
276         this._connectorType=connectorType;
277     }
278 
279     /* ------------------------------------------------------------ */
280     /**
281      * Create a new NIO buffer. If using direct buffers, it will create a direct
282      * NIO buffer, other than an indirect buffer.
283      */
284     @Override
285     protected Buffer newBuffer(int size)
286     {
287         if (_connectorType!=CONNECTOR_SOCKET)
288         {
289             Buffer buf=null;
290             if (size==getHeaderBufferSize())
291                 buf=new IndirectNIOBuffer(size);
292             else if (_useDirectBuffers)
293                 buf=new DirectNIOBuffer(size);
294             else
295                 buf=new IndirectNIOBuffer(size);
296             return buf;
297         }
298         else
299         {
300             return new ByteArrayBuffer(size);
301         }
302     }
303 
304     /* ------------------------------------------------------------ */
305     public int getMaxConnectionsPerAddress()
306     {
307         return _maxConnectionsPerAddress;
308     }
309 
310     /* ------------------------------------------------------------ */
311     public void setMaxConnectionsPerAddress(int maxConnectionsPerAddress)
312     {
313         _maxConnectionsPerAddress=maxConnectionsPerAddress;
314     }
315 
316     /* ------------------------------------------------------------ */
317     protected void doStart() throws Exception
318     {
319         super.doStart();
320 
321         _timeoutQ.setNow();
322         _timeoutQ.setDuration(_timeout);
323 
324         if(_threadPool==null)
325         {
326             QueuedThreadPool pool = new QueuedThreadPool();
327             pool.setMaxThreads(16);
328             pool.setDaemon(true);
329             pool.setName("HttpClient");
330             _threadPool=pool;
331         }
332 
333         if (_threadPool instanceof LifeCycle)
334         {
335             ((LifeCycle)_threadPool).start();
336         }
337 
338 
339         if (_connectorType==CONNECTOR_SELECT_CHANNEL)
340         {
341 
342             _connector=new SelectConnector(this);
343         }
344         else
345         {
346             _connector=new SocketConnector(this);
347         }
348         _connector.start();
349 
350         _threadPool.dispatch(new Runnable()
351         {
352             public void run()
353             {
354                 while (isRunning())
355                 {
356                     _timeoutQ.setNow();
357                     _timeoutQ.tick();
358                     try
359                     {
360                         Thread.sleep(1000);
361                     }
362                     catch (InterruptedException e)
363                     {
364                         Log.ignore(e);
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             Log.debug(e);
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             Log.debug(e);
506             throw new IOException( "issue ignoring certs" );
507         }
508     }
509 
510     /* ------------------------------------------------------------ */
511     /**
512      * @return the period in milliseconds a {@link HttpConnection} can be idle for before it is closed.
513      */
514     public long getIdleTimeout()
515     {
516         return _idleTimeout;
517     }
518 
519     /* ------------------------------------------------------------ */
520     /**
521      * @param ms the period in milliseconds a {@link HttpConnection} can be idle for before it is closed.
522      */
523     public void setIdleTimeout(long ms)
524     {
525         _idleTimeout=ms;
526     }
527 
528     /* ------------------------------------------------------------ */
529     public int getSoTimeout() 
530     {
531         return _soTimeout;
532     }
533 
534     /* ------------------------------------------------------------ */
535     public void setSoTimeout(int so)
536     {
537         _soTimeout = so;
538     }
539 
540     /* ------------------------------------------------------------ */
541     /**
542      * @return the period in ms that an exchange will wait for a response from the server.
543      */
544     public long getTimeout()
545     {
546         return _timeout;
547     }
548 
549     /* ------------------------------------------------------------ */
550     /**
551      * @param ms the period in ms that an exchange will wait for a response from the server.
552      */
553     public void setTimeout(long ms)
554     {
555         _timeout=ms;
556     }
557 
558     /* ------------------------------------------------------------ */
559     public Address getProxy()
560     {
561         return _proxy;
562     }
563 
564     /* ------------------------------------------------------------ */
565     public void setProxy(Address proxy)
566     {
567         this._proxy = proxy;
568     }
569 
570     /* ------------------------------------------------------------ */
571     public Authorization getProxyAuthentication()
572     {
573         return _proxyAuthentication;
574     }
575 
576     /* ------------------------------------------------------------ */
577     public void setProxyAuthentication(Authorization authentication)
578     {
579         _proxyAuthentication = authentication;
580     }
581 
582     /* ------------------------------------------------------------ */
583     public boolean isProxied()
584     {
585         return this._proxy!=null;
586     }
587 
588     /* ------------------------------------------------------------ */
589     public Set<String> getNoProxy()
590     {
591         return _noProxy;
592     }
593 
594     /* ------------------------------------------------------------ */
595     public void setNoProxy(Set<String> noProxyAddresses)
596     {
597         _noProxy = noProxyAddresses;
598     }
599 
600     /* ------------------------------------------------------------ */
601     public int maxRetries()
602     {
603         return _maxRetries;
604     }
605 
606      /* ------------------------------------------------------------ */
607     public void setMaxRetries( int retries )
608     {
609         _maxRetries = retries;
610     }
611 
612     /* ------------------------------------------------------------ */
613     public String getTrustStoreLocation()
614     {
615         return _trustStoreLocation;
616     }
617 
618     /* ------------------------------------------------------------ */
619     public void setTrustStoreLocation(String trustStoreLocation)
620     {
621         this._trustStoreLocation = trustStoreLocation;
622     }
623 
624     /* ------------------------------------------------------------ */
625     public String getKeyStoreLocation()
626     {
627         return _keyStoreLocation;
628     }
629 
630     /* ------------------------------------------------------------ */
631     public void setKeyStoreLocation(String keyStoreLocation)
632     {
633         this._keyStoreLocation = keyStoreLocation;
634     }
635 
636     /* ------------------------------------------------------------ */
637     public void setKeyStorePassword(String _keyStorePassword)
638     {
639         this._keyStorePassword = _keyStorePassword;
640     }
641 
642     /* ------------------------------------------------------------ */
643     public void setKeyManagerPassword(String _keyManagerPassword)
644     {
645         this._keyManagerPassword = _keyManagerPassword;
646     }
647 
648     /* ------------------------------------------------------------ */
649     public void setTrustStorePassword(String _trustStorePassword)
650     {
651         this._trustStorePassword = _trustStorePassword;
652     }
653 }