View Javadoc

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