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