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