1: 
  37: 
  38: 
  39: package ;
  40: 
  41: import ;
  42: 
  43: import ;
  44: import ;
  45: 
  46: import ;
  47: import ;
  48: import ;
  49: import ;
  50: import ;
  51: import ;
  52: import ;
  53: import ;
  54: import ;
  55: import ;
  56: import ;
  57: import ;
  58: import ;
  59: import ;
  60: import ;
  61: import ;
  62: 
  63: import ;
  64: import ;
  65: import ;
  66: import ;
  67: import ;
  68: 
  69: 
  74: public class HTTPConnection
  75: {
  76: 
  77:   
  80:   public static final int HTTP_PORT = 80;
  81: 
  82:   
  85:   public static final int HTTPS_PORT = 443;
  86: 
  87:   private static final String userAgent = SystemProperties.getProperty("http.agent");
  88: 
  89:   
  92:   protected final String hostname;
  93: 
  94:   
  97:   protected final int port;
  98: 
  99:   
 102:   protected final boolean secure;
 103: 
 104:   
 107:   protected final int connectionTimeout;
 108: 
 109:   
 112:   protected final int timeout;
 113: 
 114:   
 117:   protected String proxyHostname;
 118: 
 119:   
 122:   protected int proxyPort;
 123: 
 124:   
 127:   protected int majorVersion;
 128: 
 129:   
 132:   protected int minorVersion;
 133: 
 134:   private final List<HandshakeCompletedListener> handshakeCompletedListeners;
 135: 
 136:   
 139:   protected Socket socket;
 140: 
 141:   
 144:   private SSLSocketFactory sslSocketFactory;
 145: 
 146:   
 149:   protected InputStream in;
 150: 
 151:   
 154:   protected OutputStream out;
 155: 
 156:   
 159:   private Map<String, Integer> nonceCounts;
 160: 
 161:   
 164:   protected CookieManager cookieManager;
 165: 
 166: 
 167:   
 170:   private Pool pool;
 171: 
 172:   
 176:   public HTTPConnection(String hostname)
 177:   {
 178:     this(hostname, HTTP_PORT, false, 0, 0);
 179:   }
 180: 
 181:   
 186:   public HTTPConnection(String hostname, boolean secure)
 187:   {
 188:     this(hostname, secure ? HTTPS_PORT : HTTP_PORT, secure, 0, 0);
 189:   }
 190: 
 191:   
 198:   public HTTPConnection(String hostname, boolean secure,
 199:                         int connectionTimeout, int timeout)
 200:   {
 201:     this(hostname, secure ? HTTPS_PORT : HTTP_PORT, secure,
 202:          connectionTimeout, timeout);
 203:   }
 204: 
 205:   
 210:   public HTTPConnection(String hostname, int port)
 211:   {
 212:     this(hostname, port, false, 0, 0);
 213:   }
 214: 
 215:   
 221:   public HTTPConnection(String hostname, int port, boolean secure)
 222:   {
 223:     this(hostname, port, secure, 0, 0);
 224:   }
 225: 
 226:   
 237:   public HTTPConnection(String hostname, int port, boolean secure,
 238:                         int connectionTimeout, int timeout)
 239:   {
 240:     if (connectionTimeout < 0 || timeout < 0)
 241:       throw new IllegalArgumentException();
 242: 
 243:     this.hostname = hostname;
 244:     this.port = port;
 245:     this.secure = secure;
 246:     this.connectionTimeout = connectionTimeout;
 247:     this.timeout = timeout;
 248:     majorVersion = minorVersion = 1;
 249:     handshakeCompletedListeners
 250:       = new ArrayList<HandshakeCompletedListener>(2);
 251:   }
 252: 
 253:   
 256:   public String getHostName()
 257:   {
 258:     return hostname;
 259:   }
 260: 
 261:   
 264:   public int getPort()
 265:   {
 266:     return port;
 267:   }
 268: 
 269:   
 272:   public boolean isSecure()
 273:   {
 274:     return secure;
 275:   }
 276: 
 277:   
 282:   public String getVersion()
 283:   {
 284:     return "HTTP/" + majorVersion + '.' + minorVersion;
 285:   }
 286: 
 287:   
 292:   public void setVersion(int majorVersion, int minorVersion)
 293:   {
 294:     if (majorVersion != 1)
 295:       {
 296:         throw new IllegalArgumentException("major version not supported: " +
 297:                                            majorVersion);
 298:       }
 299:     if (minorVersion < 0 || minorVersion > 1)
 300:       {
 301:         throw new IllegalArgumentException("minor version not supported: " +
 302:                                            minorVersion);
 303:       }
 304:     this.majorVersion = majorVersion;
 305:     this.minorVersion = minorVersion;
 306:   }
 307: 
 308:   
 313:   public void setProxy(String hostname, int port)
 314:   {
 315:     proxyHostname = hostname;
 316:     proxyPort = port;
 317:   }
 318: 
 319:   
 322:   public boolean isUsingProxy()
 323:   {
 324:     return (proxyHostname != null && proxyPort > 0);
 325:   }
 326: 
 327:   
 331:   public void setCookieManager(CookieManager cookieManager)
 332:   {
 333:     this.cookieManager = cookieManager;
 334:   }
 335: 
 336:   
 339:   public CookieManager getCookieManager()
 340:   {
 341:     return cookieManager;
 342:   }
 343: 
 344:   
 353:   static class Pool
 354:   {
 355:     
 358:     static Pool instance = new Pool();
 359: 
 360:     
 363:     final LinkedList<HTTPConnection> connectionPool
 364:       = new LinkedList<HTTPConnection>();
 365: 
 366:     
 369:     int maxConnections;
 370: 
 371:     
 375:     int connectionTTL;
 376: 
 377:     
 380:     class Reaper
 381:       implements Runnable
 382:     {
 383:       public void run()
 384:       {
 385:         synchronized (Pool.this)
 386:           {
 387:             try
 388:               {
 389:                 do
 390:                   {
 391:                     while (connectionPool.size() > 0)
 392:                       {
 393:                         long currentTime = System.currentTimeMillis();
 394: 
 395:                         HTTPConnection c =
 396:                           (HTTPConnection)connectionPool.getFirst();
 397: 
 398:                         long waitTime = c.timeLastUsed
 399:                           + connectionTTL - currentTime;
 400: 
 401:                         if (waitTime <= 0)
 402:                           removeOldest();
 403:                         else
 404:                           try
 405:                             {
 406:                               Pool.this.wait(waitTime);
 407:                             }
 408:                           catch (InterruptedException _)
 409:                             {
 410:                               
 411:                             }
 412:                       }
 413:                     
 414:                     
 415:                     
 416:                     
 417:                     
 418:                     
 419:                     
 420:                     
 421:                     
 422:                     
 423:                     try
 424:                       {
 425:                         Pool.this.wait(connectionTTL);
 426:                       }
 427:                     catch (InterruptedException _)
 428:                       {
 429:                         
 430:                       }
 431:                   }
 432:                 while (connectionPool.size() > 0);
 433:               }
 434:             finally
 435:               {
 436:                 reaper = null;
 437:               }
 438:           }
 439:       }
 440:     }
 441: 
 442:     Reaper reaper;
 443: 
 444:     
 447:     private Pool()
 448:     {
 449:     }
 450: 
 451:     
 461:     private static boolean matches(HTTPConnection c,
 462:                                    String h, int p, boolean sec)
 463:     {
 464:       return h.equals(c.hostname) && (p == c.port) && (sec == c.secure);
 465:     }
 466: 
 467:     
 478:     synchronized HTTPConnection get(String host,
 479:                                     int port,
 480:                                     boolean secure,
 481:                                     int connectionTimeout, int timeout)
 482:     {
 483:       String ttl =
 484:         SystemProperties.getProperty("classpath.net.http.keepAliveTTL");
 485:       connectionTTL = 10000;
 486:       if (ttl != null && ttl.length() > 0)
 487:         try
 488:           {
 489:             int v = 1000 * Integer.parseInt(ttl);
 490:             if (v >= 0)
 491:               connectionTTL = v;
 492:           }
 493:         catch (NumberFormatException _)
 494:           {
 495:             
 496:           }
 497: 
 498:       String mc = SystemProperties.getProperty("http.maxConnections");
 499:       maxConnections = 5;
 500:       if (mc != null && mc.length() > 0)
 501:         try
 502:           {
 503:             int v = Integer.parseInt(mc);
 504:             if (v > 0)
 505:               maxConnections = v;
 506:           }
 507:         catch (NumberFormatException _)
 508:           {
 509:             
 510:           }
 511: 
 512:       HTTPConnection c = null;
 513: 
 514:       ListIterator it = connectionPool.listIterator(0);
 515:       while (it.hasNext())
 516:         {
 517:           HTTPConnection cc = (HTTPConnection)it.next();
 518:           if (matches(cc, host, port, secure))
 519:             {
 520:               c = cc;
 521:               it.remove();
 522:               
 523:               if (c.socket != null)
 524:                 try
 525:                   {
 526:                     c.socket.setSoTimeout(timeout);
 527:                   }
 528:                 catch (SocketException _)
 529:                   {
 530:                     
 531:                   }
 532:               break;
 533:             }
 534:         }
 535:       if (c == null)
 536:         {
 537:           c = new HTTPConnection(host, port, secure,
 538:                                  connectionTimeout, timeout);
 539:           c.setPool(this);
 540:         }
 541:       return c;
 542:     }
 543: 
 544:     
 550:     synchronized void put(HTTPConnection c)
 551:     {
 552:       c.timeLastUsed = System.currentTimeMillis();
 553:       connectionPool.addLast(c);
 554: 
 555:       
 556:       while (connectionPool.size() >= maxConnections)
 557:         removeOldest();
 558: 
 559:       if (connectionTTL > 0 && null == reaper) {
 560:         
 561:         
 562:         
 563:         
 564:         reaper = new Reaper();
 565:         Thread t = new Thread(reaper, "HTTPConnection.Reaper");
 566:         t.setDaemon(true);
 567:         t.start();
 568:       }
 569:     }
 570: 
 571:     
 574:     void removeOldest()
 575:     {
 576:       HTTPConnection cx = (HTTPConnection)connectionPool.removeFirst();
 577:       try
 578:         {
 579:           cx.closeConnection();
 580:         }
 581:       catch (IOException ioe)
 582:         {
 583:           
 584:         }
 585:     }
 586:   }
 587: 
 588:   
 591:   int useCount;
 592: 
 593:   
 596:   long timeLastUsed;
 597: 
 598:   
 605:   void setPool(Pool p)
 606:   {
 607:     pool = p;
 608:   }
 609: 
 610:   
 615:   void release()
 616:   {
 617:     if (pool != null)
 618:       {
 619:         useCount++;
 620:         pool.put(this);
 621: 
 622:       }
 623:     else
 624:       {
 625:         
 626:         try
 627:           {
 628:             closeConnection();
 629:           }
 630:         catch (IOException ioe)
 631:           {
 632:             
 633:           }
 634:       }
 635:   }
 636: 
 637:   
 643:   public Request newRequest(String method, String path)
 644:   {
 645:     if (method == null || method.length() == 0)
 646:       {
 647:         throw new IllegalArgumentException("method must have non-zero length");
 648:       }
 649:     if (path == null || path.length() == 0)
 650:       {
 651:         path = "/";
 652:       }
 653:     Request ret = new Request(this, method, path);
 654:     if ((secure && port != HTTPS_PORT) ||
 655:         (!secure && port != HTTP_PORT))
 656:       {
 657:         ret.setHeader("Host", hostname + ":" + port);
 658:       }
 659:     else
 660:       {
 661:         ret.setHeader("Host", hostname);
 662:       }
 663:     ret.setHeader("User-Agent", userAgent);
 664:     ret.setHeader("Connection", "keep-alive");
 665:     ret.setHeader("Accept-Encoding",
 666:                   "chunked;q=1.0, gzip;q=0.9, deflate;q=0.8, " +
 667:                   "identity;q=0.6, *;q=0");
 668:     if (cookieManager != null)
 669:       {
 670:         Cookie[] cookies = cookieManager.getCookies(hostname, secure, path);
 671:         if (cookies != null && cookies.length > 0)
 672:           {
 673:             CPStringBuilder buf = new CPStringBuilder();
 674:             buf.append("$Version=1");
 675:             for (int i = 0; i < cookies.length; i++)
 676:               {
 677:                 buf.append(',');
 678:                 buf.append(' ');
 679:                 buf.append(cookies[i].toString());
 680:               }
 681:             ret.setHeader("Cookie", buf.toString());
 682:           }
 683:       }
 684:     return ret;
 685:   }
 686: 
 687:   
 690:   public void close()
 691:     throws IOException
 692:   {
 693:     closeConnection();
 694:   }
 695: 
 696:   
 700:   protected synchronized Socket getSocket()
 701:     throws IOException
 702:   {
 703:     if (socket == null)
 704:       {
 705:         String connectHostname = hostname;
 706:         int connectPort = port;
 707:         if (isUsingProxy())
 708:           {
 709:             connectHostname = proxyHostname;
 710:             connectPort = proxyPort;
 711:           }
 712:         socket = new Socket();
 713:         InetSocketAddress address =
 714:           new InetSocketAddress(connectHostname, connectPort);
 715:         if (connectionTimeout > 0)
 716:           {
 717:             socket.connect(address, connectionTimeout);
 718:           }
 719:         else
 720:           {
 721:             socket.connect(address);
 722:           }
 723:         if (timeout > 0)
 724:           {
 725:             socket.setSoTimeout(timeout);
 726:           }
 727:         if (secure)
 728:           {
 729:             try
 730:               {
 731:                 SSLSocketFactory factory = getSSLSocketFactory();
 732:                 SSLSocket ss =
 733:                   (SSLSocket) factory.createSocket(socket, connectHostname,
 734:                                                    connectPort, true);
 735:                 String[] protocols = { "TLSv1", "SSLv3" };
 736:                 ss.setEnabledProtocols(protocols);
 737:                 ss.setUseClientMode(true);
 738:                 synchronized (handshakeCompletedListeners)
 739:                   {
 740:                     if (!handshakeCompletedListeners.isEmpty())
 741:                       {
 742:                         for (Iterator i =
 743:                              handshakeCompletedListeners.iterator();
 744:                              i.hasNext(); )
 745:                           {
 746:                             HandshakeCompletedListener l =
 747:                               (HandshakeCompletedListener) i.next();
 748:                             ss.addHandshakeCompletedListener(l);
 749:                           }
 750:                       }
 751:                   }
 752:                 ss.startHandshake();
 753:                 socket = ss;
 754:               }
 755:             catch (GeneralSecurityException e)
 756:               {
 757:                 throw new IOException(e.getMessage());
 758:               }
 759:           }
 760:         in = socket.getInputStream();
 761:         in = new BufferedInputStream(in);
 762:         out = socket.getOutputStream();
 763:         out = new BufferedOutputStream(out);
 764:       }
 765:     return socket;
 766:   }
 767: 
 768:   SSLSocketFactory getSSLSocketFactory()
 769:     throws GeneralSecurityException
 770:   {
 771:     if (sslSocketFactory == null)
 772:       {
 773:         TrustManager tm = new EmptyX509TrustManager();
 774:         SSLContext context = SSLContext.getInstance("SSL");
 775:         TrustManager[] trust = new TrustManager[] { tm };
 776:         context.init(null, trust, null);
 777:         sslSocketFactory = context.getSocketFactory();
 778:       }
 779:     return sslSocketFactory;
 780:   }
 781: 
 782:   void setSSLSocketFactory(SSLSocketFactory factory)
 783:   {
 784:     sslSocketFactory = factory;
 785:   }
 786: 
 787:   protected synchronized InputStream getInputStream()
 788:     throws IOException
 789:   {
 790:     if (socket == null)
 791:       {
 792:         getSocket();
 793:       }
 794:     return in;
 795:   }
 796: 
 797:   protected synchronized OutputStream getOutputStream()
 798:     throws IOException
 799:   {
 800:     if (socket == null)
 801:       {
 802:         getSocket();
 803:       }
 804:     return out;
 805:   }
 806: 
 807:   
 810:   protected synchronized void closeConnection()
 811:     throws IOException
 812:   {
 813:     if (socket != null)
 814:       {
 815:         try
 816:           {
 817:             socket.close();
 818:           }
 819:         finally
 820:           {
 821:             socket = null;
 822:           }
 823:       }
 824:   }
 825: 
 826:   
 830:   protected String getURI()
 831:   {
 832:     CPStringBuilder buf = new CPStringBuilder();
 833:     buf.append(secure ? "https://" : "http://");
 834:     buf.append(hostname);
 835:     if (secure)
 836:       {
 837:         if (port != HTTPConnection.HTTPS_PORT)
 838:           {
 839:             buf.append(':');
 840:             buf.append(port);
 841:           }
 842:       }
 843:     else
 844:       {
 845:         if (port != HTTPConnection.HTTP_PORT)
 846:           {
 847:             buf.append(':');
 848:             buf.append(port);
 849:           }
 850:       }
 851:     return buf.toString();
 852:   }
 853: 
 854:   
 858:   int getNonceCount(String nonce)
 859:   {
 860:     if (nonceCounts == null)
 861:       {
 862:         return 0;
 863:       }
 864:     return nonceCounts.get(nonce).intValue();
 865:   }
 866: 
 867:   
 870:   void incrementNonce(String nonce)
 871:   {
 872:     int current = getNonceCount(nonce);
 873:     if (nonceCounts == null)
 874:       {
 875:         nonceCounts = new HashMap<String, Integer>();
 876:       }
 877:     nonceCounts.put(nonce, new Integer(current + 1));
 878:   }
 879: 
 880:   
 881: 
 882:   void addHandshakeCompletedListener(HandshakeCompletedListener l)
 883:   {
 884:     synchronized (handshakeCompletedListeners)
 885:       {
 886:         handshakeCompletedListeners.add(l);
 887:       }
 888:   }
 889:   void removeHandshakeCompletedListener(HandshakeCompletedListener l)
 890:   {
 891:     synchronized (handshakeCompletedListeners)
 892:       {
 893:         handshakeCompletedListeners.remove(l);
 894:       }
 895:   }
 896: 
 897: }