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