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