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.net.InetAddress;
19  import java.net.InetSocketAddress;
20  import java.net.UnknownHostException;
21  import java.util.HashMap;
22  import java.util.Map;
23  import java.util.Set;
24  
25  import javax.net.ssl.HostnameVerifier;
26  import javax.net.ssl.SSLContext;
27  import javax.net.ssl.SSLSession;
28  import javax.net.ssl.TrustManager;
29  import javax.net.ssl.X509TrustManager;
30  
31  import org.mortbay.component.LifeCycle;
32  import org.mortbay.io.Buffer;
33  import org.mortbay.io.Buffers;
34  import org.mortbay.io.ByteArrayBuffer;
35  import org.mortbay.io.nio.NIOBuffer;
36  import org.mortbay.jetty.AbstractBuffers;
37  import org.mortbay.jetty.HttpSchemes;
38  import org.mortbay.jetty.client.security.DefaultRealmResolver;
39  import org.mortbay.jetty.client.security.SecurityRealm;
40  import org.mortbay.jetty.client.security.SecurityRealmResolver;
41  import org.mortbay.thread.QueuedThreadPool;
42  import org.mortbay.thread.ThreadPool;
43  import org.mortbay.thread.Timeout;
44  
45  /**
46   * Http Client.
47   * 
48   * HttpClient is the main active component of the client API implementation.
49   * It is the opposite of the Connectors in standard Jetty, in that it listens 
50   * for responses rather than requests.   Just like the connectors, there is a
51   * blocking socket version and a non-blocking NIO version (implemented as nested classes
52   * selected by {@link #setConnectorType(int)}).
53   * 
54   * The an instance of {@link HttpExchange} is passed to the {@link #send(HttpExchange)} method 
55   * to send a request.  The exchange contains both the headers and content (source) of the request
56   * plus the callbacks to handle responses.   A HttpClient can have many exchanges outstanding
57   * and they may be queued on the {@link HttpDestination} waiting for a {@link HttpConnection},
58   * queued in the {@link HttpConnection} waiting to be transmitted or pipelined on the actual
59   * TCP/IP connection waiting for a response.
60   * 
61   * The {@link HttpDestination} class is an aggregation of {@link HttpConnection}s for the 
62   * same host, port and protocol.   A destination may limit the number of connections 
63   * open and they provide a pool of open connections that may be reused.   Connections may also 
64   * be allocated from a destination, so that multiple request sources are not multiplexed
65   * over the same connection.
66   * 
67   * @see {@link HttpExchange}
68   * @see {@link HttpDestination}
69   * @author Greg Wilkins
70   * @author Matthew Purland
71   * @author Guillaume Nodet
72   */
73  public class HttpClient extends AbstractBuffers
74  {
75      public static final int CONNECTOR_SOCKET=0;
76      public static final int CONNECTOR_SELECT_CHANNEL=2;
77  
78      private int _connectorType=CONNECTOR_SOCKET;
79      private boolean _useDirectBuffers=true;
80      private int _maxConnectionsPerAddress=32;
81      private Map<InetSocketAddress, HttpDestination> _destinations=new HashMap<InetSocketAddress, HttpDestination>();
82      ThreadPool _threadPool;
83      Connector _connector;
84      private long _idleTimeout=20000;
85      private long _timeout=320000;
86      int _soTimeout = 10000;
87      private Timeout _timeoutQ = new Timeout();
88      private InetSocketAddress _proxy;
89      private Set<InetAddress> _noProxy;
90  
91      private SecurityRealmResolver _realmResolver;    
92  
93      
94      
95      /* ------------------------------------------------------------------------------- */
96      public void send(HttpExchange exchange) throws IOException
97      {
98          boolean ssl=HttpSchemes.HTTPS_BUFFER.equalsIgnoreCase(exchange.getScheme());
99          exchange.setStatus(HttpExchange.STATUS_WAITING_FOR_CONNECTION);
100         HttpDestination destination=getDestination(exchange.getAddress(),ssl);
101         destination.send(exchange);
102     }
103 
104     /* ------------------------------------------------------------ */
105     /**
106      * @return the threadPool
107      */
108     public ThreadPool getThreadPool()
109     {
110         return _threadPool;
111     }
112 
113     /* ------------------------------------------------------------ */
114     /**
115      * @param threadPool the threadPool to set
116      */
117     public void setThreadPool(ThreadPool threadPool)
118     {
119         _threadPool=threadPool;
120     }
121 
122     /* ------------------------------------------------------------------------------- */
123     public HttpDestination getDestination(InetSocketAddress remote, boolean ssl) throws UnknownHostException, IOException
124     {
125         if (remote==null)
126             throw new UnknownHostException("Remote socket address cannot be null.");
127 
128         synchronized (_destinations)
129         {
130             HttpDestination destination=_destinations.get(remote);
131             if (destination==null)
132             {
133                 destination=new HttpDestination(this,remote,ssl,_maxConnectionsPerAddress);
134                 if (_proxy!=null && (_noProxy==null || !_noProxy.contains(remote.getAddress())))
135                     destination.setProxy(_proxy);
136                 _destinations.put(remote,destination);
137             }
138             return destination;
139         }
140     } 
141 
142     /* ------------------------------------------------------------ */
143     public void schedule(Timeout.Task task)
144     {
145         _timeoutQ.schedule(task); 
146     }
147 
148     /* ------------------------------------------------------------ */
149     public void cancel(Timeout.Task task)
150     {
151         task.cancel();
152     }
153     
154     /* ------------------------------------------------------------ */
155     /**
156      * Get whether the connector can use direct NIO buffers.
157      */
158     public boolean getUseDirectBuffers()
159     {
160         return _useDirectBuffers;
161     }
162 
163     /* ------------------------------------------------------------ */
164     public void setSecurityRealmResolver( SecurityRealmResolver resolver )
165     {
166         _realmResolver = resolver;
167     }
168 
169     /* ------------------------------------------------------------ */
170     /**
171      * returns the SecurityRealmResolver registered with the HttpClient or null
172      * 
173      * @return 
174      */
175     public SecurityRealmResolver getSecurityRealmResolver()
176     {
177         return _realmResolver;
178     }
179     
180     /* ------------------------------------------------------------ */
181     public boolean hasRealms()
182     {
183         return _realmResolver==null?false:true;
184     }
185 
186     /* ------------------------------------------------------------ */
187     /**
188      * Set to use NIO direct buffers.
189      * 
190      * @param direct
191      *            If True (the default), the connector can use NIO direct
192      *            buffers. Some JVMs have memory management issues (bugs) with
193      *            direct buffers.
194      */
195     public void setUseDirectBuffers(boolean direct)
196     {
197         _useDirectBuffers=direct;
198     }
199 
200     /* ------------------------------------------------------------ */
201     /**
202      * Get the type of connector (socket, blocking or select) in use.
203      */
204     public int getConnectorType()
205     {
206         return _connectorType;
207     }
208 
209     /* ------------------------------------------------------------ */
210     public void setConnectorType(int connectorType)
211     {
212         this._connectorType=connectorType;
213     }
214 
215     /* ------------------------------------------------------------ */
216     /**
217      * Create a new NIO buffer. If using direct buffers, it will create a direct
218      * NIO buffer, other than an indirect buffer.
219      */
220     @Override
221     protected Buffer newBuffer(int size)
222     {
223         if (_connectorType!=CONNECTOR_SOCKET)
224         {
225             Buffer buf=null;
226             if (size==getHeaderBufferSize())
227                 buf=new NIOBuffer(size,NIOBuffer.INDIRECT);
228             else
229                 buf=new NIOBuffer(size,_useDirectBuffers?NIOBuffer.DIRECT:NIOBuffer.INDIRECT);
230             return buf;
231         }
232         else
233         {
234             return new ByteArrayBuffer(size);
235         }
236     }
237 
238     /* ------------------------------------------------------------ */
239     public int getMaxConnectionsPerAddress()
240     {
241         return _maxConnectionsPerAddress;
242     }
243 
244     /* ------------------------------------------------------------ */
245     public void setMaxConnectionsPerAddress(int maxConnectionsPerAddress)
246     {
247         _maxConnectionsPerAddress=maxConnectionsPerAddress;
248     }
249 
250     /* ------------------------------------------------------------ */
251     protected void doStart() throws Exception
252     {
253         super.doStart();
254         
255         _timeoutQ.setNow();
256         _timeoutQ.setDuration(_timeout);
257         
258         if(_threadPool==null)
259         {
260             QueuedThreadPool pool = new QueuedThreadPool();
261             pool.setMaxThreads(16);
262             pool.setDaemon(true);
263             _threadPool=pool;
264         }
265         
266         
267         if (_threadPool instanceof QueuedThreadPool)
268         {
269             ((QueuedThreadPool)_threadPool).setName("HttpClient");
270         }
271         if (_threadPool instanceof LifeCycle)
272         {
273             ((LifeCycle)_threadPool).start();
274         }
275 
276         
277         if (_connectorType==CONNECTOR_SELECT_CHANNEL)
278         {
279             
280             _connector=new SelectConnector(this);
281         }
282         else
283         {
284             _connector=new SocketConnector(this);
285         }
286         _connector.start();
287         
288         _threadPool.dispatch(new Runnable(){
289             public void run()
290             {
291                 while (isStarted())
292                 {
293                     _timeoutQ.setNow();
294                     _timeoutQ.tick();
295                     try
296                     {
297                         Thread.sleep(1000);
298                     }
299                     catch (InterruptedException e)
300                     {
301                     }
302                 }
303             }
304         });
305         
306     }
307 
308     /* ------------------------------------------------------------ */
309     protected void doStop() throws Exception
310     {
311         _connector.stop();
312         _connector=null;
313         if (_threadPool instanceof LifeCycle)
314         {
315             ((LifeCycle)_threadPool).stop();
316         }
317         for (HttpDestination destination : _destinations.values())
318         {
319             destination.close();
320         }
321         
322         _timeoutQ.cancelAll();
323         super.doStop();
324     }
325 
326     /* ------------------------------------------------------------ */
327     interface Connector extends LifeCycle
328     {
329         public void startConnection(HttpDestination destination) throws IOException;
330 
331     }
332 
333     SSLContext getLooseSSLContext() throws IOException
334     {
335         // Create a trust manager that does not validate certificate
336         // chains
337         TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager()
338         {
339             public java.security.cert.X509Certificate[] getAcceptedIssuers()
340             {
341                 return null;
342             }
343 
344             public void checkClientTrusted( java.security.cert.X509Certificate[] certs, String authType )
345             {
346             }
347 
348             public void checkServerTrusted( java.security.cert.X509Certificate[] certs, String authType )
349             {
350             }
351         } };
352 
353         HostnameVerifier hostnameVerifier = new HostnameVerifier()
354         {
355             public boolean verify( String urlHostName, SSLSession session )
356             {
357                 System.out.println( "Warning: URL Host: " + urlHostName + " vs." + session.getPeerHost() );
358                 return true;
359             }
360         };
361 
362         // Install the all-trusting trust manager
363         try
364         {
365             // TODO real trust manager
366             SSLContext sslContext = SSLContext.getInstance( "SSL" );
367             sslContext.init( null, trustAllCerts, new java.security.SecureRandom() );
368             return sslContext;
369         }
370         catch ( Exception e )
371         {
372             throw new IOException( "issue ignoring certs" );
373         }
374     }
375     
376     /* ------------------------------------------------------------ */
377     /**
378      * @return the period in milliseconds a {@link HttpConnection} can be idle for before it is closed.
379      */
380     public long getIdleTimeout()
381     {
382         return _idleTimeout;
383     }
384 
385     /* ------------------------------------------------------------ */
386     /**
387      * @param ms the period in milliseconds a {@link HttpConnection} can be idle for before it is closed.
388      */
389     public void setIdleTimeout(long ms)
390     {
391         _idleTimeout=ms;
392     }
393 
394     /* ------------------------------------------------------------ */
395     public int getSoTimeout() 
396     {
397         return _soTimeout;
398     }
399 
400     /* ------------------------------------------------------------ */
401     public void setSoTimeout(int so) 
402     {
403         _soTimeout = so;
404     }
405 
406     /* ------------------------------------------------------------ */
407     /**
408      * @return the period in ms that an exchange will wait for a response from the server.
409      */
410     public long getTimeout()
411     {
412         return _timeout;
413     }
414 
415     /* ------------------------------------------------------------ */
416     /**
417      * @param ms the period in ms that an exchange will wait for a response from the server.
418      */
419     public void setTimeout(long ms)
420     {
421         _timeout=ms;
422     }
423 
424     /* ------------------------------------------------------------ */
425     public InetSocketAddress getProxy()
426     {
427         return _proxy;
428     }
429 
430     /* ------------------------------------------------------------ */
431     public void setProxy(InetSocketAddress proxy)
432     {
433         this._proxy = proxy;
434     }
435 
436     /* ------------------------------------------------------------ */
437     public boolean isProxied()
438     {
439         return this._proxy!=null;
440     }
441 
442     /* ------------------------------------------------------------ */
443     public Set<InetAddress> getNoProxy()
444     {
445         return _noProxy;
446     }
447 
448     /* ------------------------------------------------------------ */
449     public void setNoProxy(Set<InetAddress> noProxyAddresses)
450     {
451         _noProxy = noProxyAddresses;
452     }
453 
454     /* ------------------------------------------------------------ */
455     public int maxRetries()
456     {
457         // TODO configurable
458         return 3;
459     }
460     
461     
462 
463 }