Source for gnu.inet.http.HTTPConnection

   1: /*
   2:  * HTTPConnection.java
   3:  * Copyright (C) 2004 The Free Software Foundation
   4:  * 
   5:  * This file is part of GNU inetlib, a library.
   6:  * 
   7:  * GNU inetlib is free software; you can redistribute it and/or modify
   8:  * it under the terms of the GNU General Public License as published by
   9:  * the Free Software Foundation; either version 2 of the License, or
  10:  * (at your option) any later version.
  11:  * 
  12:  * GNU inetlib is distributed in the hope that it will be useful,
  13:  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14:  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  15:  * GNU General Public License for more details.
  16:  * 
  17:  * You should have received a copy of the GNU General Public License
  18:  * along with this library; if not, write to the Free Software
  19:  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  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:  * obliged to do so.  If you do not wish to do so, delete this
  36:  * exception statement from your version.
  37:  */
  38: 
  39: package gnu.inet.http;
  40: 
  41: import java.io.BufferedInputStream;
  42: import java.io.BufferedOutputStream;
  43: import java.io.InputStream;
  44: import java.io.IOException;
  45: import java.io.OutputStream;
  46: import java.net.InetSocketAddress;
  47: import java.net.Socket;
  48: import java.security.GeneralSecurityException;
  49: import java.util.ArrayList;
  50: import java.util.Collections;
  51: import java.util.HashMap;
  52: import java.util.List;
  53: import java.util.Map;
  54: import javax.net.SocketFactory;
  55: import javax.net.ssl.SSLContext;
  56: import javax.net.ssl.SSLSocket;
  57: import javax.net.ssl.SSLSocketFactory;
  58: import javax.net.ssl.TrustManager;
  59: 
  60: import gnu.inet.http.event.ConnectionEvent;
  61: import gnu.inet.http.event.ConnectionListener;
  62: import gnu.inet.http.event.RequestEvent;
  63: import gnu.inet.http.event.RequestListener;
  64: import gnu.inet.util.EmptyX509TrustManager;
  65: 
  66: /**
  67:  * A connection to an HTTP server.
  68:  *
  69:  * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a>
  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 = initUserAgent();
  85: 
  86:   private static String initUserAgent()
  87:   {
  88:     try
  89:       {
  90:         StringBuffer buf = new StringBuffer("inetlib/1.1 (");
  91:         buf.append(System.getProperty("os.name"));
  92:         buf.append("; ");
  93:         buf.append(System.getProperty("os.arch"));
  94:         buf.append("; ");
  95:         buf.append(System.getProperty("user.language"));
  96:         buf.append(")");
  97:         return buf.toString();
  98:       }
  99:     catch (SecurityException e)
 100:       {
 101:         return "inetlib/1.1";
 102:       }
 103:   }
 104: 
 105:   /**
 106:    * The host name of the server to connect to.
 107:    */
 108:   protected final String hostname;
 109: 
 110:   /**
 111:    * The port to connect to.
 112:    */
 113:   protected final int port;
 114: 
 115:   /**
 116:    * Whether the connection should use transport level security (HTTPS).
 117:    */
 118:   protected final boolean secure;
 119: 
 120:   /**
 121:    * The connection timeout for connecting the underlying socket.
 122:    */
 123:   protected final int connectionTimeout;
 124: 
 125:   /**
 126:    * The read timeout for reads on the underlying socket.
 127:    */
 128:   protected final int timeout;
 129: 
 130:   /**
 131:    * The host name of the proxy to connect to.
 132:    */
 133:   protected String proxyHostname;
 134: 
 135:   /**
 136:    * The port on the proxy to connect to.
 137:    */
 138:   protected int proxyPort;
 139: 
 140:   /**
 141:    * The major version of HTTP supported by this client.
 142:    */
 143:   protected int majorVersion;
 144: 
 145:   /**
 146:    * The minor version of HTTP supported by this client.
 147:    */
 148:   protected int minorVersion;
 149: 
 150:   private final List connectionListeners;
 151:   private final List requestListeners;
 152: 
 153:   /**
 154:    * The socket this connection communicates on.
 155:    */
 156:   protected Socket socket;
 157: 
 158:   /**
 159:    * The socket input stream.
 160:    */
 161:   protected InputStream in;
 162: 
 163:   /**
 164:    * The socket output stream.
 165:    */
 166:   protected OutputStream out;
 167: 
 168:   /**
 169:    * Nonce values seen by this connection.
 170:    */
 171:   private Map nonceCounts;
 172: 
 173:   /**
 174:    * The cookie manager for this connection.
 175:    */
 176:   protected CookieManager cookieManager;
 177: 
 178:   /**
 179:    * Creates a new HTTP connection.
 180:    * @param hostname the name of the host to connect to
 181:    */
 182:   public HTTPConnection(String hostname)
 183:   {
 184:     this(hostname, HTTP_PORT, false, 0, 0);
 185:   }
 186: 
 187:   /**
 188:    * Creates a new HTTP or HTTPS connection.
 189:    * @param hostname the name of the host to connect to
 190:    * @param secure whether to use a secure connection
 191:    */
 192:   public HTTPConnection(String hostname, boolean secure)
 193:   {
 194:     this(hostname, secure ? HTTPS_PORT : HTTP_PORT, secure, 0, 0);
 195:   }
 196: 
 197:   /**
 198:    * Creates a new HTTP or HTTPS connection on the specified port.
 199:    * @param hostname the name of the host to connect to
 200:    * @param secure whether to use a secure connection
 201:    * @param connectionTimeout the connection timeout
 202:    * @param timeout the socket read timeout
 203:    */
 204:   public HTTPConnection(String hostname, boolean secure,
 205:                         int connectionTimeout, int timeout)
 206:   {
 207:     this(hostname, secure ? HTTPS_PORT : HTTP_PORT, secure,
 208:          connectionTimeout, timeout);
 209:   }
 210:   
 211:   /**
 212:    * Creates a new HTTP connection on the specified port.
 213:    * @param hostname the name of the host to connect to
 214:    * @param port the port on the host to connect to
 215:    */
 216:   public HTTPConnection(String hostname, int port)
 217:   {
 218:     this(hostname, port, false, 0, 0);
 219:   }
 220: 
 221:   /**
 222:    * Creates a new HTTP or HTTPS connection on the specified port.
 223:    * @param hostname the name of the host to connect to
 224:    * @param port the port on the host to connect to
 225:    * @param secure whether to use a secure connection
 226:    */
 227:   public HTTPConnection(String hostname, int port, boolean secure)
 228:   {
 229:     this(hostname, port, secure, 0, 0);
 230:   }
 231:   
 232:   /**
 233:    * Creates a new HTTP or HTTPS connection on the specified port.
 234:    * @param hostname the name of the host to connect to
 235:    * @param port the port on the host to connect to
 236:    * @param secure whether to use a secure connection
 237:    * @param connectionTimeout the connection timeout
 238:    * @param timeout the socket read timeout
 239:    */
 240:   public HTTPConnection(String hostname, int port, boolean secure,
 241:                         int connectionTimeout, int timeout)
 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:     connectionListeners = Collections.synchronizedList(new ArrayList(4));
 250:     requestListeners = Collections.synchronizedList(new ArrayList(4));
 251:   }
 252: 
 253:   /**
 254:    * Returns the name of the host to connect to.
 255:    */
 256:   public String getHostName()
 257:   {
 258:     return hostname;
 259:   }
 260: 
 261:   /**
 262:    * Returns the port on the host to connect to.
 263:    */
 264:   public int getPort()
 265:   {
 266:     return port;
 267:   }
 268: 
 269:   /**
 270:    * Indicates whether to use a secure connection or not.
 271:    */
 272:   public boolean isSecure()
 273:   {
 274:     return secure;
 275:   }
 276: 
 277:   /**
 278:    * Returns the HTTP version string supported by this connection.
 279:    * @see #version
 280:    */
 281:   public String getVersion()
 282:   {
 283:     return "HTTP/" + majorVersion + '.' + minorVersion;
 284:   }
 285: 
 286:   /**
 287:    * Sets the HTTP version supported by this connection.
 288:    * @param majorVersion the major version
 289:    * @param minorVersion the minor version
 290:    */
 291:   public void setVersion(int majorVersion, int minorVersion)
 292:   {
 293:     if (majorVersion != 1)
 294:       {
 295:         throw new IllegalArgumentException("major version not supported: " +
 296:                                            majorVersion);
 297:       }
 298:     if (minorVersion < 0 || minorVersion > 1)
 299:       {
 300:         throw new IllegalArgumentException("minor version not supported: " +
 301:                                            minorVersion);
 302:       }
 303:     this.majorVersion = majorVersion;
 304:     this.minorVersion = minorVersion;
 305:   }
 306: 
 307:   /**
 308:    * Directs this connection to use the specified proxy.
 309:    * @param hostname the proxy host name
 310:    * @param port the port on the proxy to connect to
 311:    */
 312:   public void setProxy(String hostname, int port)
 313:   {
 314:     proxyHostname = hostname;
 315:     proxyPort = port;
 316:   }
 317: 
 318:   /**
 319:    * Indicates whether this connection is using an HTTP proxy.
 320:    */
 321:   public boolean isUsingProxy()
 322:   {
 323:     return (proxyHostname != null && proxyPort > 0);
 324:   }
 325: 
 326:   /**
 327:    * Sets the cookie manager to use for this connection.
 328:    * @param cookieManager the cookie manager
 329:    */
 330:   public void setCookieManager(CookieManager cookieManager)
 331:   {
 332:     this.cookieManager = cookieManager;
 333:   }
 334: 
 335:   /**
 336:    * Returns the cookie manager in use for this connection.
 337:    */
 338:   public CookieManager getCookieManager()
 339:   {
 340:     return cookieManager;
 341:   }
 342: 
 343:   /**
 344:    * Creates a new request using this connection.
 345:    * @param method the HTTP method to invoke
 346:    * @param path the URI-escaped RFC2396 <code>abs_path</code> with
 347:    * optional query part
 348:    */
 349:   public Request newRequest(String method, String path)
 350:   {
 351:     if (method == null || method.length() == 0)
 352:       {
 353:         throw new IllegalArgumentException("method must have non-zero length");
 354:       }
 355:     if (path == null || path.length() == 0)
 356:       {
 357:         path = "/";
 358:       }
 359:     Request ret = new Request(this, method, path);
 360:     if ((secure && port != HTTPS_PORT) ||
 361:         (!secure && port != HTTP_PORT))
 362:       {
 363:         ret.setHeader("Host", hostname + ":" + port);
 364:       }
 365:     else
 366:       {
 367:         ret.setHeader("Host", hostname);
 368:       }
 369:     ret.setHeader("User-Agent", userAgent);
 370:     ret.setHeader("Connection", "keep-alive");
 371:     ret.setHeader("Accept-Encoding",
 372:                   "chunked;q=1.0, gzip;q=0.9, deflate;q=0.8, " +
 373:                   "identity;q=0.6, *;q=0");
 374:     if (cookieManager != null)
 375:       {
 376:         Cookie[] cookies = cookieManager.getCookies(hostname, secure, path);
 377:         if (cookies != null && cookies.length > 0)
 378:           {
 379:             StringBuffer buf = new StringBuffer();
 380:             buf.append("$Version=1");
 381:             for (int i = 0; i < cookies.length; i++)
 382:               {
 383:                 buf.append(',');
 384:                 buf.append(' ');
 385:                 buf.append(cookies[i].toString());
 386:               }
 387:             ret.setHeader("Cookie", buf.toString());
 388:           }
 389:       }
 390:     fireRequestEvent(RequestEvent.REQUEST_CREATED, ret);
 391:     return ret;
 392:   }
 393: 
 394:   /**
 395:    * Closes this connection.
 396:    */
 397:   public void close()
 398:     throws IOException
 399:   {
 400:     try
 401:       {
 402:         closeConnection();
 403:       }
 404:     finally
 405:       {
 406:         fireConnectionEvent(ConnectionEvent.CONNECTION_CLOSED);
 407:       }
 408:   }
 409: 
 410:   /**
 411:    * Retrieves the socket associated with this connection.
 412:    * This creates the socket if necessary.
 413:    */
 414:   protected Socket getSocket()
 415:     throws IOException
 416:   {
 417:     if (socket == null)
 418:       {
 419:         String connectHostname = hostname;
 420:         int connectPort = port;
 421:         if (isUsingProxy())
 422:           {
 423:             connectHostname = proxyHostname;
 424:             connectPort = proxyPort;
 425:           }
 426:         socket = new Socket();
 427:         InetSocketAddress address =
 428:           new InetSocketAddress(connectHostname, connectPort);
 429:         if (connectionTimeout > 0)
 430:           {
 431:             socket.connect(address, connectionTimeout);
 432:           }
 433:         else
 434:           {
 435:             socket.connect(address);
 436:           }
 437:         if (timeout > 0)
 438:           {
 439:             socket.setSoTimeout(timeout);
 440:           }
 441:         if (secure)
 442:           {
 443:             try
 444:               {
 445:                 TrustManager tm = new EmptyX509TrustManager();
 446:                 SSLContext context = SSLContext.getInstance("SSL");
 447:                 TrustManager[] trust = new TrustManager[] { tm };
 448:                 context.init(null, trust, null);
 449:                 SSLSocketFactory factory =  context.getSocketFactory();
 450:                 SSLSocket ss =
 451:                   (SSLSocket) factory.createSocket(socket, connectHostname,
 452:                                                    connectPort, true);
 453:                 String[] protocols = { "TLSv1", "SSLv3" };
 454:                 ss.setEnabledProtocols(protocols);
 455:                 ss.setUseClientMode(true);
 456:                 ss.startHandshake();
 457:                 socket = ss;
 458:               }
 459:             catch (GeneralSecurityException e)
 460:               {
 461:                 throw new IOException(e.getMessage());
 462:               }
 463:           }
 464:         in = socket.getInputStream();
 465:         in = new BufferedInputStream(in);
 466:         out = socket.getOutputStream();
 467:         out = new BufferedOutputStream(out);
 468:       }
 469:     return socket;
 470:   }
 471: 
 472:   protected InputStream getInputStream()
 473:     throws IOException
 474:   {
 475:     if (socket == null)
 476:       {
 477:         getSocket();
 478:       }
 479:     return in;
 480:   }
 481: 
 482:   protected OutputStream getOutputStream()
 483:     throws IOException
 484:   {
 485:     if (socket == null)
 486:       {
 487:         getSocket();
 488:       }
 489:     return out;
 490:   }
 491: 
 492:   /**
 493:    * Closes the underlying socket, if any.
 494:    */
 495:   protected void closeConnection()
 496:     throws IOException
 497:   {
 498:     if (socket != null)
 499:       {
 500:         try
 501:           {
 502:             socket.close();
 503:           }
 504:         finally
 505:           {
 506:             socket = null;
 507:           }
 508:       }
 509:   }
 510: 
 511:   /**
 512:    * Returns a URI representing the connection.
 513:    * This does not include any request path component.
 514:    */
 515:   protected String getURI()
 516:   {
 517:     StringBuffer buf = new StringBuffer();
 518:     buf.append(secure ? "https://" : "http://");
 519:     buf.append(hostname);
 520:     if (secure)
 521:       {
 522:         if (port != HTTPConnection.HTTPS_PORT)
 523:           {
 524:             buf.append(':');
 525:             buf.append(port);
 526:           }
 527:       }
 528:     else
 529:       {
 530:         if (port != HTTPConnection.HTTP_PORT)
 531:           {
 532:             buf.append(':');
 533:             buf.append(port);
 534:           }
 535:       }
 536:     return buf.toString();
 537:   }
 538: 
 539:   /**
 540:    * Get the number of times the specified nonce has been seen by this
 541:    * connection.
 542:    */
 543:   int getNonceCount(String nonce)
 544:   {
 545:     if (nonceCounts == null)
 546:       {
 547:         return 0;
 548:       }
 549:     return((Integer) nonceCounts.get(nonce)).intValue();
 550:   }
 551: 
 552:   /**
 553:    * Increment the number of times the specified nonce has been seen.
 554:    */
 555:   void incrementNonce(String nonce)
 556:   {
 557:     int current = getNonceCount(nonce);
 558:     if (nonceCounts == null)
 559:       {
 560:         nonceCounts = new HashMap();
 561:       }
 562:     nonceCounts.put(nonce, new Integer(current + 1));
 563:   }
 564: 
 565:   // -- Events --
 566:   
 567:   public void addConnectionListener(ConnectionListener l)
 568:   {
 569:     synchronized (connectionListeners)
 570:       {
 571:         connectionListeners.add(l);
 572:       }
 573:   }
 574: 
 575:   public void removeConnectionListener(ConnectionListener l)
 576:   {
 577:     synchronized (connectionListeners)
 578:       {
 579:         connectionListeners.remove(l);
 580:       }
 581:   }
 582: 
 583:   protected void fireConnectionEvent(int type)
 584:   {
 585:     ConnectionEvent event = new ConnectionEvent(this, type);
 586:     ConnectionListener[] l = null;
 587:     synchronized (connectionListeners)
 588:       {
 589:         l = new ConnectionListener[connectionListeners.size()];
 590:         connectionListeners.toArray(l);
 591:       }
 592:     for (int i = 0; i < l.length; i++)
 593:       {
 594:         switch (type)
 595:           {
 596:           case ConnectionEvent.CONNECTION_CLOSED:
 597:             l[i].connectionClosed(event);
 598:             break;
 599:           }
 600:       }
 601:   }
 602: 
 603:   public void addRequestListener(RequestListener l)
 604:   {
 605:     synchronized (requestListeners)
 606:       {
 607:         requestListeners.add(l);
 608:       }
 609:   }
 610: 
 611:   public void removeRequestListener(RequestListener l)
 612:   {
 613:     synchronized (requestListeners)
 614:       {
 615:         requestListeners.remove(l);
 616:       }
 617:   }
 618: 
 619:   protected void fireRequestEvent(int type, Request request)
 620:   {
 621:     RequestEvent event = new RequestEvent(this, type, request);
 622:     RequestListener[] l = null;
 623:     synchronized (requestListeners)
 624:       {
 625:         l = new RequestListener[requestListeners.size()];
 626:         requestListeners.toArray(l);
 627:       }
 628:     for (int i = 0; i < l.length; i++)
 629:       {
 630:         switch (type)
 631:           {
 632:           case RequestEvent.REQUEST_CREATED:
 633:             l[i].requestCreated(event);
 634:             break;
 635:           case RequestEvent.REQUEST_SENDING:
 636:             l[i].requestSent(event);
 637:             break;
 638:           case RequestEvent.REQUEST_SENT:
 639:             l[i].requestSent(event);
 640:             break;
 641:           }
 642:       }
 643:   }
 644: 
 645: }