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