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  package org.mortbay.jetty.client;
15  
16  
17  import java.io.IOException;
18  import java.lang.reflect.Constructor;
19  import java.net.InetSocketAddress;
20  import java.util.ArrayList;
21  import java.util.LinkedList;
22  import java.util.List;
23  import java.util.concurrent.ArrayBlockingQueue;
24  
25  import javax.servlet.http.Cookie;
26  
27  import org.mortbay.io.Buffer;
28  import org.mortbay.io.ByteArrayBuffer;
29  import org.mortbay.jetty.HttpHeaders;
30  import org.mortbay.jetty.client.security.Authorization;
31  import org.mortbay.jetty.client.security.SecurityListener;
32  import org.mortbay.jetty.servlet.PathMap;
33  import org.mortbay.log.Log;
34  
35  /**
36  *
37  * @author Greg Wilkins
38  * @author Guillaume Nodet
39  */
40  public class HttpDestination
41  {
42      private ByteArrayBuffer _hostHeader;
43      private InetSocketAddress _address;
44      private LinkedList<HttpConnection> _connections=new LinkedList<HttpConnection>();
45      private ArrayList<HttpConnection> _idle=new ArrayList<HttpConnection>();
46      private HttpClient _client;
47      private boolean _ssl;
48      private int _maxConnections;
49      private int _pendingConnections=0;
50      private ArrayBlockingQueue<Object> _newQueue = new ArrayBlockingQueue<Object>(10,true);
51      private int _newConnection=0;
52      private InetSocketAddress _proxy;
53      private Authorization _proxyAuthentication;
54      private PathMap _authorizations;
55      private List<Cookie> _cookies;
56      
57  
58  
59      public void dump() throws IOException
60      {
61          synchronized (this)
62          {
63              System.err.println(this);
64              System.err.println("connections="+_connections.size());
65              System.err.println("idle="+_idle.size());
66              System.err.println("pending="+_pendingConnections);
67              for (HttpConnection c : _connections)
68              {
69                  if (!c.isIdle())
70                      c.dump();
71              }
72          }
73      }
74      
75      /* The queue of exchanged for this destination if connections are limited */
76      private LinkedList<HttpExchange> _queue=new LinkedList<HttpExchange>();
77  
78      /* ------------------------------------------------------------ */
79      HttpDestination(HttpClient pool, InetSocketAddress address, boolean ssl, int maxConnections)
80      {
81          _client=pool;
82          _address=address;
83          _ssl=ssl;
84          _maxConnections=maxConnections;
85          String host = address.getHostName();
86          if (address.getPort() != (_ssl ? 443 : 80)) {
87              host += ":" + address.getPort();
88          }
89          _hostHeader = new ByteArrayBuffer (host);
90      }
91  
92      /* ------------------------------------------------------------ */
93      public InetSocketAddress getAddress()
94      {
95          return _address;
96      }
97  
98      /* ------------------------------------------------------------ */
99      public Buffer getHostHeader()
100     {
101         return _hostHeader;
102     }
103     
104     /* ------------------------------------------------------------ */
105     public HttpClient getHttpClient()
106     {
107         return _client;
108     }
109 
110     /* ------------------------------------------------------------ */
111     public boolean isSecure()
112     {
113         return _ssl;
114     }
115     
116     /* ------------------------------------------------------------ */
117     public void addAuthorization(String pathSpec,Authorization authorization)
118     {
119         synchronized (this)
120         {
121             if (_authorizations==null)
122                 _authorizations=new PathMap();
123             _authorizations.put(pathSpec,authorization);
124         }
125         
126         // TODO query and remove methods
127     }
128 
129     /* ------------------------------------------------------------------------------- */
130     public void addCookie(Cookie cookie)
131     {
132         synchronized (this)
133         {
134             if (_cookies==null)
135                 _cookies=new ArrayList<Cookie>();
136             _cookies.add(cookie);
137         }
138         
139         // TODO query, remove and age methods
140     }
141     
142     /* ------------------------------------------------------------------------------- */
143     public HttpConnection getConnection() throws IOException
144     {
145         HttpConnection connection = getIdleConnection();
146 
147         while (connection==null)
148         {
149             synchronized(this)
150             {
151                 _newConnection++;
152                 startNewConnection();
153             }
154 
155             try
156             {
157                 Object o =_newQueue.take();
158                 if (o instanceof HttpConnection)
159                     connection=(HttpConnection)o;
160                 else
161                     throw (IOException)o;
162             }
163             catch (InterruptedException e)
164             {
165                 Log.ignore(e);
166             }
167         }
168         return connection;
169     }
170     
171     /* ------------------------------------------------------------------------------- */
172     public HttpConnection getIdleConnection() throws IOException
173     {
174         synchronized (this)
175         {
176             long now = System.currentTimeMillis();
177             long idleTimeout=_client.getIdleTimeout();
178  
179             // Find an idle connection
180             while (_idle.size() > 0)
181             {
182                 HttpConnection connection = _idle.remove(_idle.size()-1);
183                 long last = connection.getLast();
184                 if (connection.getEndPoint().isOpen() && (last==0 || ((now-last)<idleTimeout)) )
185                     return connection;
186                 else
187                 {
188                     _connections.remove(connection);
189                     connection.getEndPoint().close();
190                 }
191             }
192 
193             return null;
194         }
195     }
196 
197     /* ------------------------------------------------------------------------------- */
198     protected void startNewConnection() 
199     {
200         try
201         {
202             synchronized (this)
203             {
204                 _pendingConnections++;
205             }
206             _client._connector.startConnection(this);
207         }
208         catch(Exception e)
209         {
210             onConnectionFailed(e);
211         }
212     }
213 
214     /* ------------------------------------------------------------------------------- */
215     public void onConnectionFailed(Throwable throwable)
216     {
217         Throwable connect_failure=null;
218         
219         synchronized (this)
220         {
221             _pendingConnections--;
222             if (_newConnection>0)
223             {
224                 connect_failure=throwable;
225                 _newConnection--;
226             }
227             else if (_queue.size()>0)
228             {
229                 HttpExchange ex=_queue.removeFirst();
230                 ex.getEventListener().onConnectionFailed(throwable);
231             }
232         }
233 
234         if(connect_failure!=null)
235         {
236             try
237             {
238                 _newQueue.put(connect_failure);
239             }
240             catch (InterruptedException e)
241             {
242                 Log.ignore(e);
243             }
244         }
245     }
246 
247     /* ------------------------------------------------------------------------------- */
248     public void onException(Throwable throwable)
249     {
250         synchronized (this)
251         {
252             _pendingConnections--;
253             if (_queue.size()>0)
254             {
255                 HttpExchange ex=_queue.removeFirst();
256                 ex.getEventListener().onException(throwable);
257                 ex.setStatus(HttpExchange.STATUS_EXCEPTED);
258             }
259         }
260     }
261     
262     /* ------------------------------------------------------------------------------- */
263     public void onNewConnection(HttpConnection connection) throws IOException
264     {
265         HttpConnection q_connection=null;
266         
267         synchronized (this)
268         {
269             _pendingConnections--;
270             _connections.add(connection);
271             
272             if (_newConnection>0)
273             {
274                 q_connection=connection;
275                 _newConnection--;
276             }
277             else if (_queue.size()==0)
278             {
279                 _idle.add(connection);
280             }
281             else
282             {
283                 HttpExchange ex=_queue.removeFirst();
284                 connection.send(ex);
285             }
286         }
287 
288         if (q_connection!=null)
289         {
290             try
291             {
292                 _newQueue.put(q_connection);
293             }
294             catch (InterruptedException e)
295             {
296                 Log.ignore(e);
297             }
298         }
299     }
300 
301     /* ------------------------------------------------------------------------------- */
302     public void returnConnection(HttpConnection connection, boolean close) throws IOException
303     {
304         if (close)
305         {
306             try
307             {
308                 connection.close();
309             }
310             catch(IOException e)
311             {
312                 Log.ignore(e);
313             }
314         }
315         
316         if (!_client.isStarted())
317             return;
318         
319         if (!close && connection.getEndPoint().isOpen())
320         {
321             synchronized (this)
322             {
323                 if (_queue.size()==0)
324                 {
325                     connection.setLast(System.currentTimeMillis());
326                     _idle.add(connection);
327                 }
328                 else
329                 {
330                     HttpExchange ex=_queue.removeFirst();
331                     connection.send(ex);
332                 }
333                 this.notifyAll();
334             }
335         }
336         else 
337         {
338             synchronized (this)
339             {
340                 _connections.remove(connection);
341                 if (!_queue.isEmpty())
342                     startNewConnection();
343             }
344         }
345     }
346     
347     /* ------------------------------------------------------------ */
348     public void send(HttpExchange ex) throws IOException
349     {
350         LinkedList<String> listeners = _client.getRegisteredListeners();
351 
352         if (listeners != null)
353         {
354             // Add registered listeners, fail if we can't load them
355             for (int i = listeners.size(); i > 0; --i)
356             {
357                 String listenerClass = listeners.get(i - 1);
358 
359                 try
360                 {
361                     Class listener = Class.forName(listenerClass);
362                     Constructor constructor = listener.getDeclaredConstructor(HttpDestination.class, HttpExchange.class);
363                     HttpEventListener elistener = (HttpEventListener) constructor.newInstance(this, ex);
364                     ex.setEventListener(elistener);
365                 }
366                 catch (Exception e)
367                 {
368                     e.printStackTrace();
369                     throw new IOException("Unable to instantiate registered listener for destination: " + listenerClass );
370                 }
371             }
372         }
373 
374         // Security is supported by default and should be the first consulted
375         if ( _client.hasRealms() )
376         {
377             ex.setEventListener( new SecurityListener( this, ex ) );
378         }
379         
380         doSend(ex);
381     }
382     
383     /* ------------------------------------------------------------ */
384     public void resend(HttpExchange ex) throws IOException
385     {
386         ex.getEventListener().onRetry();
387         doSend(ex);
388     }
389     
390     /* ------------------------------------------------------------ */
391     protected void doSend(HttpExchange ex) throws IOException
392     {
393         // add cookies
394         // TODO handle max-age etc.
395         if (_cookies!=null)
396         {
397             StringBuilder buf=null;
398             for (Cookie cookie : _cookies)
399             {
400                 if (buf==null)
401                     buf=new StringBuilder();
402                 else
403                     buf.append("; ");
404                 buf.append(cookie.getName()); // TODO quotes
405                 buf.append("=");
406                 buf.append(cookie.getValue()); // TODO quotes
407             }
408             if (buf!=null)
409                 ex.addRequestHeader(HttpHeaders.COOKIE,buf.toString());
410         }
411         
412         // Add any known authorizations
413         if (_authorizations!=null)
414         {
415             Authorization auth= (Authorization)_authorizations.match(ex.getURI());
416             if (auth !=null)
417                 ((Authorization)auth).setCredentials(ex);
418         }
419        
420         synchronized(this)
421         {
422             //System.out.println( "Sending: " + ex.toString() );
423 
424             HttpConnection connection=null;
425             if (_queue.size()>0 || (connection=getIdleConnection())==null || !connection.send(ex))
426             {
427                 _queue.add(ex);
428                 if (_connections.size()+_pendingConnections <_maxConnections)
429                 {
430                      startNewConnection();
431                 }
432             }
433         }
434     }
435 
436     /* ------------------------------------------------------------ */
437     public synchronized String toString()
438     {
439         return "HttpDestination@"+hashCode()+"//"+_address.getHostName()+":"+_address.getPort()+"("+_connections.size()+","+_idle.size()+","+_queue.size()+")";
440     }
441     
442     /* ------------------------------------------------------------ */
443     public synchronized String toDetailString()
444     {
445         StringBuilder b = new StringBuilder();
446         b.append(toString());
447         b.append('\n');
448         synchronized(this)
449         {
450             for (HttpConnection connection : _connections)
451             {
452                 if (connection._exchange!=null)
453                 {
454                     b.append(connection.toDetailString());
455                     if (_idle.contains(connection))
456                         b.append(" IDLE");
457                     b.append('\n');
458                 }
459             }
460         }
461         b.append("--");
462         b.append('\n');
463         
464         return b.toString();
465     }
466 
467     /* ------------------------------------------------------------ */
468     public void setProxy(InetSocketAddress proxy)
469     {
470         _proxy=proxy;
471     }
472 
473     /* ------------------------------------------------------------ */
474     public InetSocketAddress getProxy()
475     {
476         return _proxy;
477     }
478 
479     /* ------------------------------------------------------------ */
480     public Authorization getProxyAuthentication()
481     {
482         return _proxyAuthentication;
483     }
484 
485     /* ------------------------------------------------------------ */
486     public void setProxyAuthentication(Authorization authentication)
487     {
488         _proxyAuthentication = authentication;
489     }
490 
491     /* ------------------------------------------------------------ */
492     public boolean isProxied()
493     {
494         return _proxy!=null;
495     }
496 
497     /* ------------------------------------------------------------ */
498     public void close() throws IOException
499     {
500         synchronized (this)
501         {
502             for (HttpConnection connection : _connections)
503             {
504                 connection.close();
505             }
506         }
507     }
508     
509 }