1   package org.mortbay.jetty.client;
2   //========================================================================
3   //Copyright 2006-2007 Mort Bay Consulting Pty. Ltd.
4   //------------------------------------------------------------------------
5   //Licensed under the Apache License, Version 2.0 (the "License");
6   //you may not use this file except in compliance with the License.
7   //You may obtain a copy of the License at
8   //http://www.apache.org/licenses/LICENSE-2.0
9   //Unless required by applicable law or agreed to in writing, software
10  //distributed under the License is distributed on an "AS IS" BASIS,
11  //WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  //See the License for the specific language governing permissions and
13  //limitations under the License.
14  //========================================================================
15  
16  import java.io.IOException;
17  import java.net.InetSocketAddress;
18  import java.util.ArrayList;
19  import java.util.LinkedList;
20  import java.util.List;
21  import java.util.concurrent.ArrayBlockingQueue;
22  
23  import javax.servlet.http.Cookie;
24  
25  import org.mortbay.io.Buffer;
26  import org.mortbay.io.ByteArrayBuffer;
27  import org.mortbay.jetty.HttpHeaders;
28  import org.mortbay.jetty.client.security.Authentication;
29  import org.mortbay.jetty.client.security.SecurityListener;
30  import org.mortbay.jetty.servlet.PathMap;
31  import org.mortbay.log.Log;
32  
33  /**
34  *
35  * @author Greg Wilkins
36  * @author Guillaume Nodet
37  */
38  public class HttpDestination
39  {
40      private ByteArrayBuffer _hostHeader;
41      private InetSocketAddress _address;
42      private LinkedList<HttpConnection> _connections=new LinkedList<HttpConnection>();
43      private ArrayList<HttpConnection> _idle=new ArrayList<HttpConnection>();
44      private HttpClient _client;
45      private 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 InetSocketAddress _proxy;
51      private PathMap _authorizations;
52      private List<Cookie> _cookies;
53      
54      // TODO add a repository of cookies and authenticated credentials
55      
56      
57      /* The queue of exchanged for this destination if connections are limited */
58      private LinkedList<HttpExchange> _queue=new LinkedList<HttpExchange>();
59  
60      /* ------------------------------------------------------------ */
61      HttpDestination(HttpClient pool, InetSocketAddress address, boolean ssl, int maxConnections)
62      {
63          _client=pool;
64          _address=address;
65          _ssl=ssl;
66          _maxConnections=maxConnections;
67          String host = address.getHostName();
68          if (address.getPort() != (_ssl ? 443 : 80)) {
69              host += ":" + address.getPort();
70          }
71          _hostHeader = new ByteArrayBuffer (host);
72      }
73  
74      /* ------------------------------------------------------------ */
75      public InetSocketAddress getAddress()
76      {
77          return _address;
78      }
79  
80      /* ------------------------------------------------------------ */
81      public Buffer getHostHeader()
82      {
83          return _hostHeader;
84      }
85      
86      /* ------------------------------------------------------------ */
87      public HttpClient getHttpClient()
88      {
89          return _client;
90      }
91  
92      /* ------------------------------------------------------------ */
93      public boolean isSecure()
94      {
95          return _ssl;
96      }
97      
98      /* ------------------------------------------------------------ */
99      public void addAuthorization(String pathSpec,Authentication authentication)
100     {
101         synchronized (this)
102         {
103             if (_authorizations==null)
104                 _authorizations=new PathMap();
105             _authorizations.put(pathSpec,authentication);
106         }
107         
108         // TODO query and remove methods
109     }
110 
111     /* ------------------------------------------------------------------------------- */
112     public void addCookie(Cookie cookie)
113     {
114         synchronized (this)
115         {
116             if (_cookies==null)
117                 _cookies=new ArrayList<Cookie>();
118             _cookies.add(cookie);
119         }
120         
121         // TODO query, remove and age methods
122     }
123     
124     /* ------------------------------------------------------------------------------- */
125     public HttpConnection getConnection() throws IOException
126     {
127         HttpConnection connection = getIdleConnection();
128 
129         while (connection==null)
130         {
131             synchronized(this)
132             {
133                 _newConnection++;
134                 startNewConnection();
135             }
136 
137             try
138             {
139                 Object o =_newQueue.take();
140                 if (o instanceof HttpConnection)
141                     connection=(HttpConnection)o;
142                 else
143                     throw (IOException)o;
144             }
145             catch (InterruptedException e)
146             {
147                 Log.ignore(e);
148             }
149         }
150         return connection;
151     }
152     
153     /* ------------------------------------------------------------------------------- */
154     public HttpConnection getIdleConnection() throws IOException
155     {
156         synchronized (this)
157         {
158             long now = System.currentTimeMillis();
159             long idleTimeout=_client.getIdleTimeout();
160  
161             // Find an idle connection
162             while (_idle.size() > 0)
163             {
164                 HttpConnection connection = _idle.remove(_idle.size()-1);
165                 long last = connection.getLast();
166                 if (connection.getEndPoint().isOpen() && (last==0 || ((now-last)<idleTimeout)) )
167                     return connection;
168                 else
169                 {
170                     _connections.remove(connection);
171                     connection.getEndPoint().close();
172                 }
173             }
174 
175             return null;
176         }
177     }
178 
179     /* ------------------------------------------------------------------------------- */
180     protected void startNewConnection() 
181     {
182         try
183         {
184             synchronized (this)
185             {
186                 _pendingConnections++;
187             }
188             _client._connector.startConnection(this);
189         }
190         catch(Exception e)
191         {
192             onConnectionFailed(e);
193         }
194     }
195 
196     /* ------------------------------------------------------------------------------- */
197     public void onConnectionFailed(Throwable throwable)
198     {
199         Throwable connect_failure=null;
200         
201         synchronized (this)
202         {
203             _pendingConnections--;
204             if (_newConnection>0)
205             {
206                 connect_failure=throwable;
207                 _newConnection--;
208             }
209             else if (_queue.size()>0)
210             {
211                 HttpExchange ex=_queue.removeFirst();
212                 ex.getEventListener().onConnectionFailed(throwable);
213             }
214         }
215 
216         if(connect_failure!=null)
217         {
218             try
219             {
220                 _newQueue.put(connect_failure);
221             }
222             catch (InterruptedException e)
223             {
224                 Log.ignore(e);
225             }
226         }
227     }
228 
229     /* ------------------------------------------------------------------------------- */
230     public void onException(Throwable throwable)
231     {
232         synchronized (this)
233         {
234             _pendingConnections--;
235             if (_queue.size()>0)
236             {
237                 HttpExchange ex=_queue.removeFirst();
238                 ex.getEventListener().onException(throwable);
239                 ex.setStatus(HttpExchange.STATUS_EXCEPTED);
240             }
241         }
242     }
243     
244     /* ------------------------------------------------------------------------------- */
245     public void onNewConnection(HttpConnection connection) throws IOException
246     {
247         HttpConnection q_connection=null;
248         
249         synchronized (this)
250         {
251             _pendingConnections--;
252             _connections.add(connection);
253             
254             if (_newConnection>0)
255             {
256                 q_connection=connection;
257                 _newConnection--;
258             }
259             else if (_queue.size()==0)
260             {
261                 _idle.add(connection);
262             }
263             else
264             {
265                 HttpExchange ex=_queue.removeFirst();
266                 connection.send(ex);
267             }
268         }
269 
270         if (q_connection!=null)
271         {
272             try
273             {
274                 _newQueue.put(q_connection);
275             }
276             catch (InterruptedException e)
277             {
278                 Log.ignore(e);
279             }
280         }
281     }
282 
283     /* ------------------------------------------------------------------------------- */
284     public void returnConnection(HttpConnection connection, boolean close) throws IOException
285     {
286         if (close)
287         {
288             try
289             {
290                 connection.close();
291             }
292             catch(IOException e)
293             {
294                 Log.ignore(e);
295             }
296         }
297         
298         if (!_client.isStarted())
299             return;
300         
301         if (!close && connection.getEndPoint().isOpen())
302         {
303             synchronized (this)
304             {
305                 if (_queue.size()==0)
306                 {
307                     connection.setLast(System.currentTimeMillis());
308                     _idle.add(connection);
309                 }
310                 else
311                 {
312                     HttpExchange ex=_queue.removeFirst();
313                     connection.send(ex);
314                 }
315                 this.notifyAll();
316             }
317         }
318         else 
319         {
320             synchronized (this)
321             {
322                 _connections.remove(connection);
323                 if (!_queue.isEmpty())
324                     startNewConnection();
325             }
326         }
327     }
328     
329     /* ------------------------------------------------------------ */
330     public void send(HttpExchange ex) throws IOException
331     {
332         if (_client.hasRealms())
333             ex.setEventListener(new SecurityListener(this,ex));
334 
335         doSend(ex);
336     }
337     
338     /* ------------------------------------------------------------ */
339     public void resend(HttpExchange ex) throws IOException
340     {
341         ex.getEventListener().onRetry();
342         doSend(ex);
343     }
344     
345     /* ------------------------------------------------------------ */
346     protected void doSend(HttpExchange ex) throws IOException
347     {
348         // add cookies
349         // TODO handle max-age etc.
350         if (_cookies!=null)
351         {
352             StringBuilder buf=null;
353             for (Cookie cookie : _cookies)
354             {
355                 if (buf==null)
356                     buf=new StringBuilder();
357                 else
358                     buf.append("; ");
359                 buf.append(cookie.getName()); // TODO quotes
360                 buf.append("=");
361                 buf.append(cookie.getValue()); // TODO quotes
362             }
363             if (buf!=null)
364                 ex.addRequestHeader(HttpHeaders.COOKIE,buf.toString());
365         }
366         
367         // Add any known authorizations
368         if (_authorizations!=null)
369         {
370             Authentication auth= (Authentication)_authorizations.match(ex.getURI());
371             if (auth !=null)
372                 ((Authentication)auth).setCredentials(ex);
373         }
374        
375         synchronized(this)
376         {
377             HttpConnection connection=null;
378             if (_queue.size()>0 || (connection=getIdleConnection())==null || !connection.send(ex))
379             {
380                 _queue.add(ex);
381                 if (_connections.size()+_pendingConnections <_maxConnections)
382                 {
383                      startNewConnection();
384                 }
385             }
386         }
387     }
388 
389     /* ------------------------------------------------------------ */
390     public synchronized String toString()
391     {
392         return "HttpDestination@"+hashCode()+"//"+_address.getHostName()+":"+_address.getPort()+"("+_connections.size()+","+_idle.size()+","+_queue.size()+")";
393     }
394     
395     /* ------------------------------------------------------------ */
396     public synchronized String toDetailString()
397     {
398         StringBuilder b = new StringBuilder();
399         b.append(toString());
400         b.append('\n');
401         synchronized(this)
402         {
403             for (HttpConnection connection : _connections)
404             {
405                 if (connection._exchange!=null)
406                 {
407                     b.append(connection.toDetailString());
408                     if (_idle.contains(connection))
409                         b.append(" IDLE");
410                     b.append('\n');
411                 }
412             }
413         }
414         b.append("--");
415         b.append('\n');
416         
417         return b.toString();
418     }
419 
420     /* ------------------------------------------------------------ */
421     public void setProxy(InetSocketAddress proxy)
422     {
423         _proxy=proxy;
424     }
425 
426     /* ------------------------------------------------------------ */
427     public InetSocketAddress getProxy()
428     {
429         return _proxy;
430     }
431     
432     /* ------------------------------------------------------------ */
433     public boolean isProxied()
434     {
435         return _proxy!=null;
436     }
437 
438     /* ------------------------------------------------------------ */
439     public void close() throws IOException
440     {
441         synchronized (this)
442         {
443             for (HttpConnection connection : _connections)
444             {
445                 connection.close();
446             }
447         }
448     }
449     
450 }