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.HashMap;
23  import java.util.LinkedList;
24  import java.util.Map;
25  import java.util.Set;
26  
27  import javax.net.ssl.HostnameVerifier;
28  import javax.net.ssl.KeyManager;
29  import javax.net.ssl.KeyManagerFactory;
30  import javax.net.ssl.SSLContext;
31  import javax.net.ssl.SSLSession;
32  import javax.net.ssl.TrustManager;
33  import javax.net.ssl.TrustManagerFactory;
34  import javax.net.ssl.X509TrustManager;
35  
36  import org.mortbay.component.LifeCycle;
37  import org.mortbay.io.Buffer;
38  import org.mortbay.io.ByteArrayBuffer;
39  import org.mortbay.io.nio.DirectNIOBuffer;
40  import org.mortbay.io.nio.IndirectNIOBuffer;
41  import org.mortbay.io.nio.NIOBuffer;
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  
52  /**
53   * Http Client.
54   * <p/>
55   * HttpClient is the main active component of the client API implementation.
56   * It is the opposite of the Connectors in standard Jetty, in that it listens
57   * for responses rather than requests.   Just like the connectors, there is a
58   * blocking socket version and a non-blocking NIO version (implemented as nested classes
59   * selected by {@link #setConnectorType(int)}).
60   * <p/>
61   * The an instance of {@link HttpExchange} is passed to the {@link #send(HttpExchange)} method
62   * to send a request.  The exchange contains both the headers and content (source) of the request
63   * plus the callbacks to handle responses.   A HttpClient can have many exchanges outstanding
64   * and they may be queued on the {@link HttpDestination} waiting for a {@link HttpConnection},
65   * queued in the {@link HttpConnection} waiting to be transmitted or pipelined on the actual
66   * TCP/IP connection waiting for a response.
67   * <p/>
68   * The {@link HttpDestination} class is an aggregation of {@link HttpConnection}s for the
69   * same host, port and protocol.   A destination may limit the number of connections
70   * open and they provide a pool of open connections that may be reused.   Connections may also
71   * be allocated from a destination, so that multiple request sources are not multiplexed
72   * over the same connection.
73   *
74   * @author Greg Wilkins
75   * @author Matthew Purland
76   * @author Guillaume Nodet
77   * @see {@link HttpExchange}
78   * @see {@link HttpDestination}
79   */
80  public class HttpClient extends AbstractBuffers
81  {
82      public static final int CONNECTOR_SOCKET = 0;
83      public static final int CONNECTOR_SELECT_CHANNEL = 2;
84  
85      private int _connectorType = CONNECTOR_SELECT_CHANNEL;
86      private boolean _useDirectBuffers = true;
87      private int _maxConnectionsPerAddress = 32;
88      private Map<Address, HttpDestination> _destinations = new HashMap<Address, HttpDestination>();
89      ThreadPool _threadPool;
90      Connector _connector;
91      private long _idleTimeout = 20000;
92      private long _timeout = 320000;
93      int _soTimeout = 10000;
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     public void dump() throws IOException
121     {
122         for (Map.Entry<Address, 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(Address 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.getHost())))
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     /* ------------------------------------------------------------ */
181     public void schedule(Timeout.Task task)
182     {
183         _timeoutQ.schedule(task);
184     }
185 
186     /* ------------------------------------------------------------ */
187     public void cancel(Timeout.Task task)
188     {
189         task.cancel();
190     }
191 
192     /* ------------------------------------------------------------ */
193     /**
194      * Get whether the connector can use direct NIO buffers.
195      */
196     public boolean getUseDirectBuffers()
197     {
198         return _useDirectBuffers;
199     }
200 
201     /* ------------------------------------------------------------ */
202     public void setRealmResolver(RealmResolver resolver)
203     {
204         _realmResolver = resolver;
205     }
206 
207     /* ------------------------------------------------------------ */
208     /**
209      * returns the SecurityRealmResolver registered with the HttpClient or null
210      *
211      * @return
212      */
213     public RealmResolver getRealmResolver()
214     {
215         return _realmResolver;
216     }
217 
218     /* ------------------------------------------------------------ */
219     public boolean hasRealms()
220     {
221         return _realmResolver == null ? false : true;
222     }
223 
224 
225     /**
226      * Registers a listener that can listen to the stream of execution between the client and the
227      * server and influence events.  Sequential calls to the method wrapper sequentially wrap the preceeding
228      * listener in a delegation model.
229      * <p/>
230      * NOTE: the SecurityListener is a special listener which doesn't need to be added via this
231      * mechanic, if you register security realms then it will automatically be added as the top listener of the
232      * delegation stack.
233      *
234      * @param listenerClass
235      */
236     public void registerListener(String listenerClass)
237     {
238         if (_registeredListeners == null)
239         {
240             _registeredListeners = new LinkedList<String>();
241         }
242         _registeredListeners.add(listenerClass);
243     }
244 
245     public LinkedList<String> getRegisteredListeners()
246     {
247         return _registeredListeners;
248     }
249 
250 
251     /* ------------------------------------------------------------ */
252     /**
253      * Set to use NIO direct buffers.
254      *
255      * @param direct 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 IndirectNIOBuffer(size);
292             else if (_useDirectBuffers)
293                 buf=new DirectNIOBuffer(size);
294             else
295                 buf=new IndirectNIOBuffer(size);
296             return buf;
297         }
298         else
299         {
300             return new ByteArrayBuffer(size);
301         }
302     }
303 
304     /* ------------------------------------------------------------ */
305     public int getMaxConnectionsPerAddress()
306     {
307         return _maxConnectionsPerAddress;
308     }
309 
310     /* ------------------------------------------------------------ */
311     public void setMaxConnectionsPerAddress(int maxConnectionsPerAddress)
312     {
313         _maxConnectionsPerAddress = maxConnectionsPerAddress;
314     }
315 
316     /* ------------------------------------------------------------ */
317     protected void doStart() throws Exception
318     {
319         super.doStart();
320 
321         _timeoutQ.setNow();
322         _timeoutQ.setDuration(_timeout);
323 
324         if (_threadPool == null)
325         {
326             QueuedThreadPool pool = new QueuedThreadPool();
327             pool.setMaxThreads(16);
328             pool.setDaemon(true);
329             pool.setName("HttpClient");
330             _threadPool = pool;
331         }
332 
333         if (_threadPool instanceof LifeCycle)
334         {
335             ((LifeCycle)_threadPool).start();
336         }
337 
338 
339         if (_connectorType == CONNECTOR_SELECT_CHANNEL)
340         {
341 
342             _connector = new SelectConnector(this);
343         }
344         else
345         {
346             _connector = new SocketConnector(this);
347         }
348         _connector.start();
349 
350         _threadPool.dispatch(new Runnable()
351         {
352             public void run()
353             {
354                 while (isStarted())
355                 {
356                     _timeoutQ.setNow();
357                     _timeoutQ.tick();
358                     try
359                     {
360                         Thread.sleep(1000);
361                     }
362                     catch (InterruptedException e)
363                     {
364                     }
365                 }
366             }
367         });
368 
369     }
370 
371     /* ------------------------------------------------------------ */
372     protected void doStop() throws Exception
373     {
374         _connector.stop();
375         _connector = null;
376         if (_threadPool instanceof LifeCycle)
377         {
378             ((LifeCycle)_threadPool).stop();
379         }
380         for (HttpDestination destination : _destinations.values())
381         {
382             destination.close();
383         }
384 
385         _timeoutQ.cancelAll();
386         super.doStop();
387     }
388 
389     /* ------------------------------------------------------------ */
390     interface Connector extends LifeCycle
391     {
392         public void startConnection(HttpDestination destination) throws IOException;
393 
394     }
395 
396     /**
397      * if a keystore location has been provided then client will attempt to use it as the keystore,
398      * otherwise we simply ignore certificates and run with a loose ssl context.
399      *
400      * @return
401      * @throws IOException
402      */
403     protected SSLContext getSSLContext() throws IOException
404     {
405     	if (_sslContext == null) 
406     	{
407 			if (_keyStoreLocation == null) 
408 			{
409 				_sslContext = getLooseSSLContext();
410 			} 
411 			else 
412 			{
413 				_sslContext = getStrictSSLContext();
414 			}
415 		}   	
416     	return _sslContext;    	
417     }
418 
419     protected SSLContext getStrictSSLContext() throws IOException
420     {
421 
422         try
423         {
424             if (_trustStoreLocation == null)
425             {
426                 _trustStoreLocation = _keyStoreLocation;
427                 _trustStoreType = _keyStoreType;
428             }
429 
430             KeyManager[] keyManagers = null;
431             InputStream keystoreInputStream = null;
432 
433             keystoreInputStream = Resource.newResource(_keyStoreLocation).getInputStream();
434             KeyStore keyStore = KeyStore.getInstance(_keyStoreType);
435             keyStore.load(keystoreInputStream, _keyStorePassword == null ? null : _keyStorePassword.toString().toCharArray());
436 
437             KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(_keyManagerAlgorithm);
438             keyManagerFactory.init(keyStore, _keyManagerPassword == null ? null : _keyManagerPassword.toString().toCharArray());
439             keyManagers = keyManagerFactory.getKeyManagers();
440 
441             TrustManager[] trustManagers = null;
442             InputStream truststoreInputStream = null;
443 
444             truststoreInputStream = Resource.newResource(_trustStoreLocation).getInputStream();
445             KeyStore trustStore = KeyStore.getInstance(_trustStoreType);
446             trustStore.load(truststoreInputStream, _trustStorePassword == null ? null : _trustStorePassword.toString().toCharArray());
447 
448             TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(_trustManagerAlgorithm);
449             trustManagerFactory.init(trustStore);
450             trustManagers = trustManagerFactory.getTrustManagers();
451 
452             SecureRandom secureRandom = _secureRandomAlgorithm == null ? null : SecureRandom.getInstance(_secureRandomAlgorithm);
453             SSLContext context = _provider == null ? SSLContext.getInstance(_protocol) : SSLContext.getInstance(_protocol, _provider);
454             context.init(keyManagers, trustManagers, secureRandom);
455             return context;
456         }
457         catch (Exception e)
458         {
459             e.printStackTrace();
460             throw new IOException("error generating ssl context for " + _keyStoreLocation + " " + e.getMessage());
461         }
462     }
463 
464     protected SSLContext getLooseSSLContext() throws IOException
465     {
466 
467         // Create a trust manager that does not validate certificate
468         // chains
469         TrustManager[] trustAllCerts = new TrustManager[]{new X509TrustManager()
470         {
471             public java.security.cert.X509Certificate[] getAcceptedIssuers()
472             {
473                 return null;
474             }
475 
476             public void checkClientTrusted(java.security.cert.X509Certificate[] certs, String authType)
477             {
478             }
479 
480             public void checkServerTrusted(java.security.cert.X509Certificate[] certs, String authType)
481             {
482             }
483         }};
484 
485         HostnameVerifier hostnameVerifier = new HostnameVerifier()
486         {
487             public boolean verify(String urlHostName, SSLSession session)
488             {
489                 Log.warn("Warning: URL Host: " + urlHostName + " vs." + session.getPeerHost());
490                 return true;
491             }
492         };
493 
494         // Install the all-trusting trust manager
495         try
496         {
497             // TODO real trust manager
498             SSLContext sslContext = SSLContext.getInstance("SSL");
499             sslContext.init(null, trustAllCerts, new java.security.SecureRandom());
500             return sslContext;
501         }
502         catch (Exception e)
503         {
504             throw new IOException("issue ignoring certs");
505         }
506     }
507 
508     /* ------------------------------------------------------------ */
509     /**
510      * @return the period in milliseconds a {@link HttpConnection} can be idle for before it is closed.
511      */
512     public long getIdleTimeout()
513     {
514         return _idleTimeout;
515     }
516 
517     /* ------------------------------------------------------------ */
518     /**
519      * @param ms the period in milliseconds a {@link HttpConnection} can be idle for before it is closed.
520      */
521     public void setIdleTimeout(long ms)
522     {
523         _idleTimeout = ms;
524     }
525 
526     /* ------------------------------------------------------------ */
527     public int getSoTimeout()
528     {
529         return _soTimeout;
530     }
531 
532     /* ------------------------------------------------------------ */
533     public void setSoTimeout(int so)
534     {
535         _soTimeout = so;
536     }
537 
538     /* ------------------------------------------------------------ */
539     /**
540      * @return the period in ms that an exchange will wait for a response from the server.
541      */
542     public long getTimeout()
543     {
544         return _timeout;
545     }
546 
547     /* ------------------------------------------------------------ */
548     /**
549      * @param ms the period in ms that an exchange will wait for a response from the server.
550      */
551     public void setTimeout(long ms)
552     {
553         _timeout = ms;
554     }
555 
556     /* ------------------------------------------------------------ */
557     public Address getProxy()
558     {
559         return _proxy;
560     }
561 
562     /* ------------------------------------------------------------ */
563     public void setProxy(Address proxy)
564     {
565         this._proxy = proxy;
566     }
567 
568     /* ------------------------------------------------------------ */
569     public Authorization getProxyAuthentication()
570     {
571         return _proxyAuthentication;
572     }
573 
574     /* ------------------------------------------------------------ */
575     public void setProxyAuthentication(Authorization authentication)
576     {
577         _proxyAuthentication = authentication;
578     }
579 
580     /* ------------------------------------------------------------ */
581     public boolean isProxied()
582     {
583         return this._proxy != null;
584     }
585 
586     /* ------------------------------------------------------------ */
587     public Set<String> getNoProxy()
588     {
589         return _noProxy;
590     }
591 
592     /* ------------------------------------------------------------ */
593     public void setNoProxy(Set<String> noProxyAddresses)
594     {
595         _noProxy = noProxyAddresses;
596     }
597 
598     /* ------------------------------------------------------------ */
599     public int maxRetries()
600     {
601         return _maxRetries;
602     }
603 
604     /* ------------------------------------------------------------ */
605     public void setMaxRetries(int retries)
606     {
607         _maxRetries = retries;
608     }
609 
610     public String getTrustStoreLocation()
611     {
612         return _trustStoreLocation;
613     }
614 
615     public void setTrustStoreLocation(String trustStoreLocation)
616     {
617         this._trustStoreLocation = trustStoreLocation;
618     }
619 
620     public String getKeyStoreLocation()
621     {
622         return _keyStoreLocation;
623     }
624 
625     public void setKeyStoreLocation(String keyStoreLocation)
626     {
627         this._keyStoreLocation = keyStoreLocation;
628     }
629 
630     public void setKeyStorePassword(String _keyStorePassword)
631     {
632         this._keyStorePassword = _keyStorePassword;
633     }
634 
635     public void setKeyManagerPassword(String _keyManagerPassword)
636     {
637         this._keyManagerPassword = _keyManagerPassword;
638     }
639 
640     public void setTrustStorePassword(String _trustStorePassword)
641     {
642         this._trustStorePassword = _trustStorePassword;
643     }
644 }