Source for gnu.java.net.protocol.http.HTTPConnection

   1: /* HTTPConnection.java --
   2:    Copyright (C) 2004, 2005, 2006  Free Software Foundation, Inc.
   3: 
   4: This file is part of GNU Classpath.
   5: 
   6: GNU Classpath is free software; you can redistribute it and/or modify
   7: it under the terms of the GNU General Public License as published by
   8: the Free Software Foundation; either version 2, or (at your option)
   9: any later version.
  10:  
  11: GNU Classpath is distributed in the hope that it will be useful, but
  12: WITHOUT ANY WARRANTY; without even the implied warranty of
  13: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  14: General Public License for more details.
  15: 
  16: You should have received a copy of the GNU General Public License
  17: along with GNU Classpath; see the file COPYING.  If not, write to the
  18: Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
  19: 02110-1301 USA.
  20: 
  21: Linking this library statically or dynamically with other modules is
  22: making a combined work based on this library.  Thus, the terms and
  23: conditions of the GNU General Public License cover the whole
  24: combination.
  25: 
  26: As a special exception, the copyright holders of this library give you
  27: permission to link this library with independent modules to produce an
  28: executable, regardless of the license terms of these independent
  29: modules, and to copy and distribute the resulting executable under
  30: terms of your choice, provided that you also meet, for each linked
  31: independent module, the terms and conditions of the license of that
  32: module.  An independent module is a module which is not derived from
  33: or based on this library.  If you modify this library, you may extend
  34: this exception to your version of the library, but you are not
  35: obligated to do so.  If you do not wish to do so, delete this
  36: exception statement from your version. */
  37: 
  38: 
  39: package gnu.java.net.protocol.http;
  40: 
  41: import gnu.classpath.SystemProperties;
  42: import gnu.java.net.EmptyX509TrustManager;
  43: 
  44: import java.io.BufferedInputStream;
  45: import java.io.BufferedOutputStream;
  46: import java.io.IOException;
  47: import java.io.InputStream;
  48: import java.io.OutputStream;
  49: import java.net.InetSocketAddress;
  50: import java.net.Socket;
  51: import java.security.GeneralSecurityException;
  52: import java.util.ArrayList;
  53: import java.util.HashMap;
  54: import java.util.Iterator;
  55: import java.util.LinkedList;
  56: import java.util.List;
  57: import java.util.ListIterator;
  58: import java.util.Map;
  59: 
  60: import javax.net.ssl.HandshakeCompletedListener;
  61: import javax.net.ssl.SSLContext;
  62: import javax.net.ssl.SSLSocket;
  63: import javax.net.ssl.SSLSocketFactory;
  64: import javax.net.ssl.TrustManager;
  65: 
  66: /**
  67:  * A connection to an HTTP server.
  68:  *
  69:  * @author Chris Burdess (dog@gnu.org)
  70:  */
  71: public class HTTPConnection
  72: {
  73: 
  74:   /**
  75:    * The default HTTP port.
  76:    */
  77:   public static final int HTTP_PORT = 80;
  78: 
  79:   /**
  80:    * The default HTTPS port.
  81:    */
  82:   public static final int HTTPS_PORT = 443;
  83: 
  84:   private static final String userAgent = SystemProperties.getProperty("http.agent");
  85: 
  86:   /**
  87:    * The host name of the server to connect to.
  88:    */
  89:   protected final String hostname;
  90: 
  91:   /**
  92:    * The port to connect to.
  93:    */
  94:   protected final int port;
  95: 
  96:   /**
  97:    * Whether the connection should use transport level security (HTTPS).
  98:    */
  99:   protected final boolean secure;
 100: 
 101:   /**
 102:    * The connection timeout for connecting the underlying socket.
 103:    */
 104:   protected final int connectionTimeout;
 105: 
 106:   /**
 107:    * The read timeout for reads on the underlying socket.
 108:    */
 109:   protected final int timeout;
 110: 
 111:   /**
 112:    * The host name of the proxy to connect to.
 113:    */
 114:   protected String proxyHostname;
 115: 
 116:   /**
 117:    * The port on the proxy to connect to.
 118:    */
 119:   protected int proxyPort;
 120: 
 121:   /**
 122:    * The major version of HTTP supported by this client.
 123:    */
 124:   protected int majorVersion;
 125: 
 126:   /**
 127:    * The minor version of HTTP supported by this client.
 128:    */
 129:   protected int minorVersion;
 130: 
 131:   private final List handshakeCompletedListeners;
 132: 
 133:   /**
 134:    * The socket this connection communicates on.
 135:    */
 136:   protected Socket socket;
 137: 
 138:   /**
 139:    * The SSL socket factory to use.
 140:    */
 141:   private SSLSocketFactory sslSocketFactory;
 142: 
 143:   /**
 144:    * The socket input stream.
 145:    */
 146:   protected InputStream in;
 147: 
 148:   /**
 149:    * The socket output stream.
 150:    */
 151:   protected OutputStream out;
 152: 
 153:   /**
 154:    * Nonce values seen by this connection.
 155:    */
 156:   private Map nonceCounts;
 157: 
 158:   /**
 159:    * The cookie manager for this connection.
 160:    */
 161:   protected CookieManager cookieManager;
 162: 
 163: 
 164:   /**
 165:    * The pool that this connection is a member of (if any).
 166:    */
 167:   private Pool pool;
 168: 
 169:   /**
 170:    * Creates a new HTTP connection.
 171:    * @param hostname the name of the host to connect to
 172:    */
 173:   public HTTPConnection(String hostname)
 174:   {
 175:     this(hostname, HTTP_PORT, false, 0, 0);
 176:   }
 177: 
 178:   /**
 179:    * Creates a new HTTP or HTTPS connection.
 180:    * @param hostname the name of the host to connect to
 181:    * @param secure whether to use a secure connection
 182:    */
 183:   public HTTPConnection(String hostname, boolean secure)
 184:   {
 185:     this(hostname, secure ? HTTPS_PORT : HTTP_PORT, secure, 0, 0);
 186:   }
 187: 
 188:   /**
 189:    * Creates a new HTTP or HTTPS connection on the specified port.
 190:    * @param hostname the name of the host to connect to
 191:    * @param secure whether to use a secure connection
 192:    * @param connectionTimeout the connection timeout
 193:    * @param timeout the socket read timeout
 194:    */
 195:   public HTTPConnection(String hostname, boolean secure,
 196:                         int connectionTimeout, int timeout)
 197:   {
 198:     this(hostname, secure ? HTTPS_PORT : HTTP_PORT, secure,
 199:          connectionTimeout, timeout);
 200:   }
 201:   
 202:   /**
 203:    * Creates a new HTTP connection on the specified port.
 204:    * @param hostname the name of the host to connect to
 205:    * @param port the port on the host to connect to
 206:    */
 207:   public HTTPConnection(String hostname, int port)
 208:   {
 209:     this(hostname, port, false, 0, 0);
 210:   }
 211: 
 212:   /**
 213:    * Creates a new HTTP or HTTPS connection on the specified port.
 214:    * @param hostname the name of the host to connect to
 215:    * @param port the port on the host to connect to
 216:    * @param secure whether to use a secure connection
 217:    */
 218:   public HTTPConnection(String hostname, int port, boolean secure)
 219:   {
 220:     this(hostname, port, secure, 0, 0);
 221:   }
 222:   
 223:   /**
 224:    * Creates a new HTTP or HTTPS connection on the specified port.
 225:    * @param hostname the name of the host to connect to
 226:    * @param port the port on the host to connect to
 227:    * @param secure whether to use a secure connection
 228:    * @param connectionTimeout the connection timeout
 229:    * @param timeout the socket read timeout
 230:    */
 231:   public HTTPConnection(String hostname, int port, boolean secure,
 232:                         int connectionTimeout, int timeout)
 233:   {
 234:     this.hostname = hostname;
 235:     this.port = port;
 236:     this.secure = secure;
 237:     this.connectionTimeout = connectionTimeout;
 238:     this.timeout = timeout;
 239:     majorVersion = minorVersion = 1;
 240:     handshakeCompletedListeners = new ArrayList(2);
 241:   }
 242: 
 243:   /**
 244:    * Returns the name of the host to connect to.
 245:    */
 246:   public String getHostName()
 247:   {
 248:     return hostname;
 249:   }
 250: 
 251:   /**
 252:    * Returns the port on the host to connect to.
 253:    */
 254:   public int getPort()
 255:   {
 256:     return port;
 257:   }
 258: 
 259:   /**
 260:    * Indicates whether to use a secure connection or not.
 261:    */
 262:   public boolean isSecure()
 263:   {
 264:     return secure;
 265:   }
 266: 
 267:   /**
 268:    * Returns the HTTP version string supported by this connection.
 269:    * @see #majorVersion
 270:    * @see #minorVersion
 271:    */
 272:   public String getVersion()
 273:   {
 274:     return "HTTP/" + majorVersion + '.' + minorVersion;
 275:   }
 276: 
 277:   /**
 278:    * Sets the HTTP version supported by this connection.
 279:    * @param majorVersion the major version
 280:    * @param minorVersion the minor version
 281:    */
 282:   public void setVersion(int majorVersion, int minorVersion)
 283:   {
 284:     if (majorVersion != 1)
 285:       {
 286:         throw new IllegalArgumentException("major version not supported: " +
 287:                                            majorVersion);
 288:       }
 289:     if (minorVersion < 0 || minorVersion > 1)
 290:       {
 291:         throw new IllegalArgumentException("minor version not supported: " +
 292:                                            minorVersion);
 293:       }
 294:     this.majorVersion = majorVersion;
 295:     this.minorVersion = minorVersion;
 296:   }
 297: 
 298:   /**
 299:    * Directs this connection to use the specified proxy.
 300:    * @param hostname the proxy host name
 301:    * @param port the port on the proxy to connect to
 302:    */
 303:   public void setProxy(String hostname, int port)
 304:   {
 305:     proxyHostname = hostname;
 306:     proxyPort = port;
 307:   }
 308: 
 309:   /**
 310:    * Indicates whether this connection is using an HTTP proxy.
 311:    */
 312:   public boolean isUsingProxy()
 313:   {
 314:     return (proxyHostname != null && proxyPort > 0);
 315:   }
 316: 
 317:   /**
 318:    * Sets the cookie manager to use for this connection.
 319:    * @param cookieManager the cookie manager
 320:    */
 321:   public void setCookieManager(CookieManager cookieManager)
 322:   {
 323:     this.cookieManager = cookieManager;
 324:   }
 325: 
 326:   /**
 327:    * Returns the cookie manager in use for this connection.
 328:    */
 329:   public CookieManager getCookieManager()
 330:   {
 331:     return cookieManager;
 332:   }
 333: 
 334:   /**
 335:    * Manages a pool of HTTPConections.  The pool will have a maximum
 336:    * size determined by the value of the maxConn parameter passed to
 337:    * the {@link #get} method.  This value inevitably comes from the
 338:    * http.maxConnections system property.  If the
 339:    * classpath.net.http.keepAliveTTL system property is set, that will
 340:    * be the maximum time (in seconds) that an idle connection will be
 341:    * maintained.
 342:    */
 343:   static class Pool
 344:   {
 345:     /**
 346:      * Singleton instance of the pool.
 347:      */
 348:     static Pool instance = new Pool();
 349: 
 350:     /**
 351:      * The pool
 352:      */
 353:     final LinkedList connectionPool = new LinkedList();
 354: 
 355:     /**
 356:      * Maximum size of the pool.
 357:      */
 358:     int maxConnections;
 359: 
 360:     /**
 361:      * If greater than zero, the maximum time a connection will remain
 362:      * int the pool.
 363:      */
 364:     int connectionTTL;
 365: 
 366:     /**
 367:      * A thread that removes connections older than connectionTTL.
 368:      */
 369:     class Reaper
 370:       implements Runnable
 371:     {
 372:       public void run()
 373:       {
 374:         synchronized (Pool.this)
 375:           {
 376:             try
 377:               {
 378:                 do
 379:                   {
 380:                     while (connectionPool.size() > 0)
 381:                       {
 382:                         long currentTime = System.currentTimeMillis();
 383: 
 384:                         HTTPConnection c =
 385:                           (HTTPConnection)connectionPool.getFirst();
 386: 
 387:                         long waitTime = c.timeLastUsed
 388:                           + connectionTTL - currentTime;
 389: 
 390:                         if (waitTime <= 0)
 391:                           removeOldest();
 392:                         else
 393:                           try
 394:                             {
 395:                               Pool.this.wait(waitTime);
 396:                             }
 397:                           catch (InterruptedException _)
 398:                             {
 399:                               // Ignore the interrupt.
 400:                             }
 401:                       }
 402:                     // After the pool is empty, wait TTL to see if it
 403:                     // is used again.  This is because in the
 404:                     // situation where a single thread is making HTTP
 405:                     // requests to the same server it can remove the
 406:                     // connection from the pool before the Reaper has
 407:                     // a chance to start.  This would cause the Reaper
 408:                     // to exit if it were not for this extra delay.
 409:                     // The result would be starting a Reaper thread
 410:                     // for each HTTP request.  With the delay we get
 411:                     // at most one Reaper created each TTL.
 412:                     try
 413:                       {
 414:                         Pool.this.wait(connectionTTL);
 415:                       }
 416:                     catch (InterruptedException _)
 417:                       {
 418:                         // Ignore the interrupt.
 419:                       }
 420:                   }
 421:                 while (connectionPool.size() > 0);
 422:               }
 423:             finally
 424:               {
 425:                 reaper = null;
 426:               }
 427:           }
 428:       }
 429:     }
 430: 
 431:     Reaper reaper;
 432: 
 433:     /**
 434:      * Private constructor to ensure singleton.
 435:      */
 436:     private Pool()
 437:     {
 438:     }
 439: 
 440:     /**
 441:      * Tests for a matching connection.
 442:      *
 443:      * @param c connection to match.
 444:      * @param h the host name.
 445:      * @param p the port.
 446:      * @param sec true if using https.
 447:      *
 448:      * @return true if c matches h, p, and sec.
 449:      */
 450:     private static boolean matches(HTTPConnection c,
 451:                                    String h, int p, boolean sec)
 452:     {
 453:       return h.equals(c.hostname) && (p == c.port) && (sec == c.secure);
 454:     }
 455: 
 456:     /**
 457:      * Get a pooled HTTPConnection.  If there is an existing idle
 458:      * connection to the requested server it is returned.  Otherwise a
 459:      * new connection is created.
 460:      *
 461:      * @param host the name of the host to connect to
 462:      * @param port the port on the host to connect to
 463:      * @param secure whether to use a secure connection
 464:      *
 465:      * @return the HTTPConnection.
 466:      */
 467:     synchronized HTTPConnection get(String host,
 468:                                     int port,
 469:                                     boolean secure, 
 470:                     int connectionTimeout, int timeout)
 471:     {
 472:       String ttl =
 473:         SystemProperties.getProperty("classpath.net.http.keepAliveTTL");
 474:       connectionTTL = (ttl != null && ttl.length() > 0) ?
 475:         1000 * Math.max(1, Integer.parseInt(ttl)) : 10000;
 476: 
 477:       String mc = SystemProperties.getProperty("http.maxConnections");
 478:       maxConnections = (mc != null && mc.length() > 0) ?
 479:         Math.max(Integer.parseInt(mc), 1) : 5;
 480:       if (maxConnections < 1)
 481:         maxConnections =  1;
 482: 
 483:       HTTPConnection c = null;
 484:       
 485:       ListIterator it = connectionPool.listIterator(0);
 486:       while (it.hasNext())
 487:         {
 488:           HTTPConnection cc = (HTTPConnection)it.next();
 489:           if (matches(cc, host, port, secure))
 490:             {
 491:               c = cc;
 492:               it.remove();
 493:               break;
 494:             }
 495:         }
 496:       if (c == null)
 497:         {
 498:           c = new HTTPConnection(host, port, secure, connectionTimeout, timeout);
 499:           c.setPool(this);
 500:         }
 501:       return c;
 502:     }
 503: 
 504:     /**
 505:      * Put an idle HTTPConnection back into the pool.  If this causes
 506:      * the pool to be come too large, the oldest connection is removed
 507:      * and closed.
 508:      *
 509:      */
 510:     synchronized void put(HTTPConnection c)
 511:     {
 512:       c.timeLastUsed = System.currentTimeMillis();
 513:       connectionPool.addLast(c);
 514: 
 515:       // maxConnections must always be >= 1
 516:       while (connectionPool.size() >= maxConnections)
 517:         removeOldest();
 518: 
 519:       if (connectionTTL > 0 && null == reaper) {
 520:         // If there is a connectionTTL, then the reaper has removed
 521:         // any stale connections, so we don't have to check for stale
 522:         // now.  We do have to start a reaper though, as there is not
 523:         // one running now.
 524:         reaper = new Reaper();
 525:         Thread t = new Thread(reaper, "HTTPConnection.Reaper");
 526:         t.setDaemon(true);
 527:         t.start();
 528:       }
 529:     }
 530: 
 531:     /**
 532:      * Remove the oldest connection from the pool and close it.
 533:      */
 534:     void removeOldest()
 535:     {
 536:       HTTPConnection cx = (HTTPConnection)connectionPool.removeFirst();
 537:       try
 538:         {
 539:           cx.closeConnection();
 540:         }
 541:       catch (IOException ioe)
 542:         {
 543:           // Ignore it.  We are just cleaning up.
 544:         }
 545:     }
 546:   }
 547:   
 548:   /**
 549:    * The number of times this HTTPConnection has be used via keep-alive.
 550:    */
 551:   int useCount;
 552: 
 553:   /**
 554:    * If this HTTPConnection is in the pool, the time it was put there.
 555:    */
 556:   long timeLastUsed;
 557: 
 558:   /**
 559:    * Set the connection pool that this HTTPConnection is a member of.
 560:    * If left unset or set to null, it will not be a member of any pool
 561:    * and will not be a candidate for reuse.
 562:    *
 563:    * @param p the pool.
 564:    */
 565:   void setPool(Pool p)
 566:   {
 567:     pool = p;
 568:   }
 569: 
 570:   /**
 571:    * Signal that this HTTPConnection is no longer needed and can be
 572:    * returned to the connection pool.
 573:    *
 574:    */
 575:   void release()
 576:   {
 577:     if (pool != null)
 578:       {
 579:         useCount++;
 580:         pool.put(this);
 581:         
 582:       }
 583:     else
 584:       {
 585:         // If there is no pool, just close.
 586:         try
 587:           {
 588:             closeConnection();
 589:           }
 590:         catch (IOException ioe)
 591:           {
 592:             // Ignore it.  We are just cleaning up.
 593:           }
 594:       }
 595:   }
 596: 
 597:   /**
 598:    * Creates a new request using this connection.
 599:    * @param method the HTTP method to invoke
 600:    * @param path the URI-escaped RFC2396 <code>abs_path</code> with
 601:    * optional query part
 602:    */
 603:   public Request newRequest(String method, String path)
 604:   {
 605:     if (method == null || method.length() == 0)
 606:       {
 607:         throw new IllegalArgumentException("method must have non-zero length");
 608:       }
 609:     if (path == null || path.length() == 0)
 610:       {
 611:         path = "/";
 612:       }
 613:     Request ret = new Request(this, method, path);
 614:     if ((secure && port != HTTPS_PORT) ||
 615:         (!secure && port != HTTP_PORT))
 616:       {
 617:         ret.setHeader("Host", hostname + ":" + port);
 618:       }
 619:     else
 620:       {
 621:         ret.setHeader("Host", hostname);
 622:       }
 623:     ret.setHeader("User-Agent", userAgent);
 624:     ret.setHeader("Connection", "keep-alive");
 625:     ret.setHeader("Accept-Encoding",
 626:                   "chunked;q=1.0, gzip;q=0.9, deflate;q=0.8, " +
 627:                   "identity;q=0.6, *;q=0");
 628:     if (cookieManager != null)
 629:       {
 630:         Cookie[] cookies = cookieManager.getCookies(hostname, secure, path);
 631:         if (cookies != null && cookies.length > 0)
 632:           {
 633:             StringBuilder buf = new StringBuilder();
 634:             buf.append("$Version=1");
 635:             for (int i = 0; i < cookies.length; i++)
 636:               {
 637:                 buf.append(',');
 638:                 buf.append(' ');
 639:                 buf.append(cookies[i].toString());
 640:               }
 641:             ret.setHeader("Cookie", buf.toString());
 642:           }
 643:       }
 644:     return ret;
 645:   }
 646: 
 647:   /**
 648:    * Closes this connection.
 649:    */
 650:   public void close()
 651:     throws IOException
 652:   {
 653:     closeConnection();
 654:   }
 655: 
 656:   /**
 657:    * Retrieves the socket associated with this connection.
 658:    * This creates the socket if necessary.
 659:    */
 660:   protected synchronized Socket getSocket()
 661:     throws IOException
 662:   {
 663:     if (socket == null)
 664:       {
 665:         String connectHostname = hostname;
 666:         int connectPort = port;
 667:         if (isUsingProxy())
 668:           {
 669:             connectHostname = proxyHostname;
 670:             connectPort = proxyPort;
 671:           }
 672:         socket = new Socket();
 673:         InetSocketAddress address =
 674:           new InetSocketAddress(connectHostname, connectPort);
 675:         if (connectionTimeout > 0)
 676:           {
 677:             socket.connect(address, connectionTimeout);
 678:           }
 679:         else
 680:           {
 681:             socket.connect(address);
 682:           }
 683:         if (timeout > 0)
 684:           {
 685:             socket.setSoTimeout(timeout);
 686:           }
 687:         if (secure)
 688:           {
 689:             try
 690:               {
 691:                 SSLSocketFactory factory = getSSLSocketFactory();
 692:                 SSLSocket ss =
 693:                   (SSLSocket) factory.createSocket(socket, connectHostname,
 694:                                                    connectPort, true);
 695:                 String[] protocols = { "TLSv1", "SSLv3" };
 696:                 ss.setEnabledProtocols(protocols);
 697:                 ss.setUseClientMode(true);
 698:                 synchronized (handshakeCompletedListeners)
 699:                   {
 700:                     if (!handshakeCompletedListeners.isEmpty())
 701:                       {
 702:                         for (Iterator i =
 703:                              handshakeCompletedListeners.iterator();
 704:                              i.hasNext(); )
 705:                           {
 706:                             HandshakeCompletedListener l =
 707:                               (HandshakeCompletedListener) i.next();
 708:                             ss.addHandshakeCompletedListener(l);
 709:                           }
 710:                       }
 711:                   }
 712:                 ss.startHandshake();
 713:                 socket = ss;
 714:               }
 715:             catch (GeneralSecurityException e)
 716:               {
 717:                 throw new IOException(e.getMessage());
 718:               }
 719:           }
 720:         in = socket.getInputStream();
 721:         in = new BufferedInputStream(in);
 722:         out = socket.getOutputStream();
 723:         out = new BufferedOutputStream(out);
 724:       }
 725:     return socket;
 726:   }
 727: 
 728:   SSLSocketFactory getSSLSocketFactory()
 729:     throws GeneralSecurityException
 730:   {
 731:     if (sslSocketFactory == null)
 732:       {
 733:         TrustManager tm = new EmptyX509TrustManager();
 734:         SSLContext context = SSLContext.getInstance("SSL");
 735:         TrustManager[] trust = new TrustManager[] { tm };
 736:         context.init(null, trust, null);
 737:         sslSocketFactory = context.getSocketFactory();
 738:       }
 739:     return sslSocketFactory;
 740:   }
 741: 
 742:   void setSSLSocketFactory(SSLSocketFactory factory)
 743:   {
 744:     sslSocketFactory = factory;
 745:   }
 746: 
 747:   protected synchronized InputStream getInputStream()
 748:     throws IOException
 749:   {
 750:     if (socket == null)
 751:       {
 752:         getSocket();
 753:       }
 754:     return in;
 755:   }
 756: 
 757:   protected synchronized OutputStream getOutputStream()
 758:     throws IOException
 759:   {
 760:     if (socket == null)
 761:       {
 762:         getSocket();
 763:       }
 764:     return out;
 765:   }
 766: 
 767:   /**
 768:    * Closes the underlying socket, if any.
 769:    */
 770:   protected synchronized void closeConnection()
 771:     throws IOException
 772:   {
 773:     if (socket != null)
 774:       {
 775:         try
 776:           {
 777:             socket.close();
 778:           }
 779:         finally
 780:           {
 781:             socket = null;
 782:           }
 783:       }
 784:   }
 785: 
 786:   /**
 787:    * Returns a URI representing the connection.
 788:    * This does not include any request path component.
 789:    */
 790:   protected String getURI()
 791:   {
 792:     StringBuilder buf = new StringBuilder();
 793:     buf.append(secure ? "https://" : "http://");
 794:     buf.append(hostname);
 795:     if (secure)
 796:       {
 797:         if (port != HTTPConnection.HTTPS_PORT)
 798:           {
 799:             buf.append(':');
 800:             buf.append(port);
 801:           }
 802:       }
 803:     else
 804:       {
 805:         if (port != HTTPConnection.HTTP_PORT)
 806:           {
 807:             buf.append(':');
 808:             buf.append(port);
 809:           }
 810:       }
 811:     return buf.toString();
 812:   }
 813: 
 814:   /**
 815:    * Get the number of times the specified nonce has been seen by this
 816:    * connection.
 817:    */
 818:   int getNonceCount(String nonce)
 819:   {
 820:     if (nonceCounts == null)
 821:       {
 822:         return 0;
 823:       }
 824:     return((Integer) nonceCounts.get(nonce)).intValue();
 825:   }
 826: 
 827:   /**
 828:    * Increment the number of times the specified nonce has been seen.
 829:    */
 830:   void incrementNonce(String nonce)
 831:   {
 832:     int current = getNonceCount(nonce);
 833:     if (nonceCounts == null)
 834:       {
 835:         nonceCounts = new HashMap();
 836:       }
 837:     nonceCounts.put(nonce, new Integer(current + 1));
 838:   }
 839: 
 840:   // -- Events --
 841:   
 842:   void addHandshakeCompletedListener(HandshakeCompletedListener l)
 843:   {
 844:     synchronized (handshakeCompletedListeners)
 845:       {
 846:         handshakeCompletedListeners.add(l);
 847:       }
 848:   }
 849:   void removeHandshakeCompletedListener(HandshakeCompletedListener l)
 850:   {
 851:     synchronized (handshakeCompletedListeners)
 852:       {
 853:         handshakeCompletedListeners.remove(l);
 854:       }
 855:   }
 856: 
 857: }