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     protected Buffer newResponseHeader(int size)
383     {
384         if (_connectorType == CONNECTOR_SOCKET)
385             return new ByteArrayBuffer(size);
386         return new IndirectNIOBuffer(size);
387     }
388 
389     /* ------------------------------------------------------------------------------- */
390     protected boolean isRequestHeader(Buffer buffer)
391     {
392         if (_connectorType == CONNECTOR_SOCKET)
393             return buffer instanceof ByteArrayBuffer;
394         return buffer instanceof IndirectNIOBuffer;
395     }
396 
397     /* ------------------------------------------------------------------------------- */
398     protected boolean isResponseHeader(Buffer buffer)
399     {
400         if (_connectorType == CONNECTOR_SOCKET)
401             return buffer instanceof ByteArrayBuffer;
402         return buffer instanceof IndirectNIOBuffer;
403     }
404     
405     
406     /* ------------------------------------------------------------ */
407     public int getMaxConnectionsPerAddress()
408     {
409         return _maxConnectionsPerAddress;
410     }
411 
412     /* ------------------------------------------------------------ */
413     public void setMaxConnectionsPerAddress(int maxConnectionsPerAddress)
414     {
415         _maxConnectionsPerAddress = maxConnectionsPerAddress;
416     }
417 
418     /* ------------------------------------------------------------ */
419     protected void doStart() throws Exception
420     {
421         super.doStart();
422 
423         _timeoutQ.setNow();
424         _timeoutQ.setDuration(_timeout);
425 
426         if (_threadPool == null)
427         {
428             QueuedThreadPool pool = new QueuedThreadPool();
429             pool.setMaxThreads(16);
430             pool.setDaemon(true);
431             pool.setName("HttpClient");
432             _threadPool = pool;
433         }
434 
435         if (_threadPool instanceof LifeCycle)
436         {
437             ((LifeCycle)_threadPool).start();
438         }
439 
440 
441         if (_connectorType == CONNECTOR_SELECT_CHANNEL)
442         {
443 
444             _connector = new SelectConnector(this);
445         }
446         else
447         {
448             _connector = new SocketConnector(this);
449         }
450         _connector.start();
451 
452         _threadPool.dispatch(new Runnable()
453         {
454             public void run()
455             {
456                 while (isRunning())
457                 {
458                     _timeoutQ.setNow();
459                     _timeoutQ.tick();
460                     try
461                     {
462                         Thread.sleep(1000);
463                     }
464                     catch (InterruptedException e)
465                     {
466                     }
467                 }
468             }
469         });
470 
471     }
472 
473     /* ------------------------------------------------------------ */
474     protected void doStop() throws Exception
475     {
476         _connector.stop();
477         _connector = null;
478         if (_threadPool instanceof LifeCycle)
479         {
480             ((LifeCycle)_threadPool).stop();
481         }
482         for (HttpDestination destination : _destinations.values())
483         {
484             destination.close();
485         }
486 
487         _timeoutQ.cancelAll();
488         super.doStop();
489     }
490 
491     /* ------------------------------------------------------------ */
492     interface Connector extends LifeCycle
493     {
494         public void startConnection(HttpDestination destination) throws IOException;
495 
496     }
497 
498     /**
499      * if a keystore location has been provided then client will attempt to use it as the keystore,
500      * otherwise we simply ignore certificates and run with a loose ssl context.
501      *
502      * @return
503      * @throws IOException
504      */
505     protected SSLContext getSSLContext() throws IOException
506     {
507     	if (_sslContext == null) 
508     	{
509 			if (_keyStoreLocation == null) 
510 			{
511 				_sslContext = getLooseSSLContext();
512 			} 
513 			else 
514 			{
515 				_sslContext = getStrictSSLContext();
516 			}
517 		}   	
518     	return _sslContext;    	
519     }
520 
521     protected SSLContext getStrictSSLContext() throws IOException
522     {
523 
524         try
525         {
526             if (_trustStoreLocation == null)
527             {
528                 _trustStoreLocation = _keyStoreLocation;
529                 _trustStoreType = _keyStoreType;
530             }
531 
532             KeyManager[] keyManagers = null;
533             InputStream keystoreInputStream = null;
534 
535             keystoreInputStream = Resource.newResource(_keyStoreLocation).getInputStream();
536             KeyStore keyStore = KeyStore.getInstance(_keyStoreType);
537             keyStore.load(keystoreInputStream, _keyStorePassword == null ? null : _keyStorePassword.toString().toCharArray());
538 
539             KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(_keyManagerAlgorithm);
540             keyManagerFactory.init(keyStore, _keyManagerPassword == null ? null : _keyManagerPassword.toString().toCharArray());
541             keyManagers = keyManagerFactory.getKeyManagers();
542 
543             TrustManager[] trustManagers = null;
544             InputStream truststoreInputStream = null;
545 
546             truststoreInputStream = Resource.newResource(_trustStoreLocation).getInputStream();
547             KeyStore trustStore = KeyStore.getInstance(_trustStoreType);
548             trustStore.load(truststoreInputStream, _trustStorePassword == null ? null : _trustStorePassword.toString().toCharArray());
549 
550             TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(_trustManagerAlgorithm);
551             trustManagerFactory.init(trustStore);
552             trustManagers = trustManagerFactory.getTrustManagers();
553 
554             SecureRandom secureRandom = _secureRandomAlgorithm == null ? null : SecureRandom.getInstance(_secureRandomAlgorithm);
555             SSLContext context = _provider == null ? SSLContext.getInstance(_protocol) : SSLContext.getInstance(_protocol, _provider);
556             context.init(keyManagers, trustManagers, secureRandom);
557             return context;
558         }
559         catch (Exception e)
560         {
561             e.printStackTrace();
562             throw new IOException("error generating ssl context for " + _keyStoreLocation + " " + e.getMessage());
563         }
564     }
565 
566     protected SSLContext getLooseSSLContext() throws IOException
567     {
568 
569         // Create a trust manager that does not validate certificate
570         // chains
571         TrustManager[] trustAllCerts = new TrustManager[]{new X509TrustManager()
572         {
573             public java.security.cert.X509Certificate[] getAcceptedIssuers()
574             {
575                 return null;
576             }
577 
578             public void checkClientTrusted(java.security.cert.X509Certificate[] certs, String authType)
579             {
580             }
581 
582             public void checkServerTrusted(java.security.cert.X509Certificate[] certs, String authType)
583             {
584             }
585         }};
586 
587         HostnameVerifier hostnameVerifier = new HostnameVerifier()
588         {
589             public boolean verify(String urlHostName, SSLSession session)
590             {
591                 Log.warn("Warning: URL Host: " + urlHostName + " vs." + session.getPeerHost());
592                 return true;
593             }
594         };
595 
596         // Install the all-trusting trust manager
597         try
598         {
599             // TODO real trust manager
600             SSLContext sslContext = SSLContext.getInstance("SSL");
601             sslContext.init(null, trustAllCerts, new java.security.SecureRandom());
602             return sslContext;
603         }
604         catch (Exception e)
605         {
606             throw new IOException("issue ignoring certs");
607         }
608     }
609 
610     /* ------------------------------------------------------------ */
611     /**
612      * @return the period in milliseconds a {@link HttpConnection} can be idle for before it is closed.
613      */
614     public long getIdleTimeout()
615     {
616         return _idleTimeout;
617     }
618 
619     /* ------------------------------------------------------------ */
620     /**
621      * @param ms the period in milliseconds a {@link HttpConnection} can be idle for before it is closed.
622      */
623     public void setIdleTimeout(long ms)
624     {
625         _idleTimeout = ms;
626     }
627 
628     /* ------------------------------------------------------------ */
629     public int getSoTimeout()
630     {
631         return _soTimeout;
632     }
633 
634     /* ------------------------------------------------------------ */
635     public void setSoTimeout(int so)
636     {
637         _soTimeout = so;
638     }
639 
640     /* ------------------------------------------------------------ */
641     /**
642      * @return the period in ms that an exchange will wait for a response from the server.
643      */
644     public long getTimeout()
645     {
646         return _timeout;
647     }
648 
649     /* ------------------------------------------------------------ */
650     /**
651      * @param ms the period in ms that an exchange will wait for a response from the server.
652      */
653     public void setTimeout(long ms)
654     {
655         _timeout = ms;
656     }
657 
658     /* ------------------------------------------------------------ */
659     public Address getProxy()
660     {
661         return _proxy;
662     }
663 
664     /* ------------------------------------------------------------ */
665     public void setProxy(Address proxy)
666     {
667         this._proxy = proxy;
668     }
669 
670     /* ------------------------------------------------------------ */
671     public Authorization getProxyAuthentication()
672     {
673         return _proxyAuthentication;
674     }
675 
676     /* ------------------------------------------------------------ */
677     public void setProxyAuthentication(Authorization authentication)
678     {
679         _proxyAuthentication = authentication;
680     }
681 
682     /* ------------------------------------------------------------ */
683     public boolean isProxied()
684     {
685         return this._proxy != null;
686     }
687 
688     /* ------------------------------------------------------------ */
689     public Set<String> getNoProxy()
690     {
691         return _noProxy;
692     }
693 
694     /* ------------------------------------------------------------ */
695     public void setNoProxy(Set<String> noProxyAddresses)
696     {
697         _noProxy = noProxyAddresses;
698     }
699 
700     /* ------------------------------------------------------------ */
701     public int maxRetries()
702     {
703         return _maxRetries;
704     }
705 
706     /* ------------------------------------------------------------ */
707     public void setMaxRetries(int retries)
708     {
709         _maxRetries = retries;
710     }
711 
712     public String getTrustStoreLocation()
713     {
714         return _trustStoreLocation;
715     }
716 
717     public void setTrustStoreLocation(String trustStoreLocation)
718     {
719         this._trustStoreLocation = trustStoreLocation;
720     }
721 
722     public String getKeyStoreLocation()
723     {
724         return _keyStoreLocation;
725     }
726 
727     public void setKeyStoreLocation(String keyStoreLocation)
728     {
729         this._keyStoreLocation = keyStoreLocation;
730     }
731 
732     public void setKeyStorePassword(String _keyStorePassword)
733     {
734         this._keyStorePassword = _keyStorePassword;
735     }
736 
737     public void setKeyManagerPassword(String _keyManagerPassword)
738     {
739         this._keyManagerPassword = _keyManagerPassword;
740     }
741 
742     public void setTrustStorePassword(String _trustStorePassword)
743     {
744         this._trustStorePassword = _trustStorePassword;
745     }
746 }