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