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