Source for gnu.inet.http.HTTPURLConnection

   1: /*
   2:  * HTTPURLConnection.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.ByteArrayInputStream;
  42: import java.io.ByteArrayOutputStream;
  43: import java.io.InputStream;
  44: import java.io.IOException;
  45: import java.io.OutputStream;
  46: import java.net.HttpURLConnection;
  47: import java.net.ProtocolException;
  48: import java.net.URL;
  49: import java.security.AccessController;
  50: import java.security.PrivilegedAction;
  51: import java.util.Date;
  52: import java.util.Collections;
  53: import java.util.Iterator;
  54: import java.util.LinkedHashMap;
  55: import java.util.Map;
  56: 
  57: /**
  58:  * A URLConnection that uses the HTTPConnection class.
  59:  *
  60:  * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a>
  61:  */
  62: public class HTTPURLConnection
  63:   extends HttpURLConnection
  64: {
  65:   
  66:   /**
  67:    * Pool of reusable connections, used if keepAlive is true.
  68:    */
  69:   private static final Map connectionPool = new LinkedHashMap();
  70: 
  71:   /*
  72:    * The underlying connection.
  73:    */
  74:   private HTTPConnection connection;
  75: 
  76:   private String proxyHostname;
  77:   private int proxyPort;
  78:   private String agent;
  79:   private boolean keepAlive;
  80:   private int maxConnections;
  81: 
  82:   private Request request;
  83:   private Headers requestHeaders;
  84:   private ByteArrayOutputStream requestSink;
  85:   private boolean requestMethodSetExplicitly;
  86: 
  87:   private Response response;
  88:   private ByteArrayInputStream responseSink;
  89: 
  90:   /**
  91:    * Constructor.
  92:    * @param url the URL
  93:    */
  94:   public HTTPURLConnection(URL url)
  95:   {
  96:     super(url);
  97:     requestHeaders = new Headers();
  98:     AccessController.doPrivileged(this.new GetHTTPPropertiesAction());
  99:   }
 100: 
 101:   class GetHTTPPropertiesAction
 102:     implements PrivilegedAction
 103:   {
 104: 
 105:     public Object run()
 106:     {
 107:       proxyHostname = System.getProperty("http.proxyHost");
 108:       if (proxyHostname != null && proxyHostname.length() > 0)
 109:         {
 110:           String port = System.getProperty("http.proxyPort");
 111:           if (port != null && port.length() > 0)
 112:             {
 113:               proxyPort = Integer.parseInt(port);
 114:             }
 115:           else
 116:             {
 117:               proxyHostname = null;
 118:               proxyPort = -1;
 119:             }
 120:         }
 121:       agent = System.getProperty("http.agent");
 122:       String ka = System.getProperty("http.keepAlive");
 123:       keepAlive = !(ka != null && "false".equals(ka));
 124:       String mc = System.getProperty("http.maxConnections");
 125:       maxConnections = (mc != null && mc.length() > 0) ?
 126:         Math.max(Integer.parseInt(mc), 1) : 5;
 127:       return null;
 128:     }
 129:     
 130:   }
 131: 
 132:   public void connect()
 133:     throws IOException
 134:   {
 135:     if (connected)
 136:       {
 137:         return;
 138:       }
 139:     String protocol = url.getProtocol();
 140:     boolean secure = "https".equals(protocol);
 141:     String host = url.getHost();
 142:     int port = url.getPort();
 143:     if (port < 0)
 144:       {
 145:         port = secure ? HTTPConnection.HTTPS_PORT :
 146:           HTTPConnection.HTTP_PORT;
 147:       }
 148:     String file = url.getFile();
 149:     String username = url.getUserInfo();
 150:     String password = null;
 151:     if (username != null)
 152:       {
 153:         int ci = username.indexOf(':');
 154:         if (ci != -1)
 155:           {
 156:             password = username.substring(ci + 1);
 157:             username = username.substring(0, ci);
 158:           }
 159:       }
 160:     final Credentials creds = (username == null) ? null :
 161:       new Credentials (username, password);
 162:     
 163:     boolean retry;
 164:     do
 165:       {
 166:         retry = false;
 167:         if (connection == null)
 168:           {
 169:             connection = getConnection(host, port, secure);
 170:           }
 171:         if (proxyHostname != null)
 172:           {
 173:             if (proxyPort < 0)
 174:               {
 175:                 proxyPort = secure ? HTTPConnection.HTTPS_PORT :
 176:                   HTTPConnection.HTTP_PORT;
 177:               }
 178:             connection.setProxy(proxyHostname, proxyPort);
 179:           }
 180:         request = connection.newRequest(method, file);
 181:         if (!keepAlive)
 182:           {
 183:             request.setHeader("Connection", "close");
 184:           }
 185:         if (agent != null)
 186:           {
 187:             request.setHeader("User-Agent", agent);
 188:           }
 189:         request.getHeaders().putAll(requestHeaders);
 190:         if (requestSink != null)
 191:           {
 192:             byte[] content = requestSink.toByteArray();
 193:             RequestBodyWriter writer = new ByteArrayRequestBodyWriter(content);
 194:             request.setRequestBodyWriter(writer);
 195:           }
 196:         ByteArrayResponseBodyReader reader = new ByteArrayResponseBodyReader();
 197:         request.setResponseBodyReader(reader);
 198:         if (creds != null)
 199:           {
 200:             request.setAuthenticator(new Authenticator() {
 201:               public Credentials getCredentials(String realm, int attempts)
 202:               {
 203:                 return (attempts < 2) ? creds : null;
 204:               }
 205:             });
 206:           }
 207:         response = request.dispatch();
 208:         if (response.getCodeClass() == 3 && getInstanceFollowRedirects())
 209:           {
 210:             // Follow redirect
 211:             String location = response.getHeader("Location");
 212:             String connectionUri = connection.getURI();
 213:             int start = connectionUri.length();
 214:             if (location.startsWith(connectionUri) &&
 215:                 location.charAt(start) == '/')
 216:               {
 217:                 file = location.substring(start);
 218:                 retry = true;
 219:               }
 220:             else if (location.startsWith("http:"))
 221:               {
 222:                 connection.close();
 223:                 connection = null;
 224:                 secure = false;
 225:                 start = 7;
 226:                 int end = location.indexOf('/', start);
 227:                 host = location.substring(start, end);
 228:                 int ci = host.lastIndexOf(':');
 229:                 if (ci != -1)
 230:                   {
 231:                     port = Integer.parseInt(host.substring (ci + 1));
 232:                     host = host.substring(0, ci);
 233:                   }
 234:                 else
 235:                   {
 236:                     port = HTTPConnection.HTTP_PORT;
 237:                   }
 238:                 file = location.substring(end);
 239:                 retry = true;
 240:               }
 241:             else if (location.startsWith("https:"))
 242:               {
 243:                 connection.close();
 244:                 connection = null;
 245:                 secure = true;
 246:                 start = 8;
 247:                 int end = location.indexOf('/', start);
 248:                 host = location.substring(start, end);
 249:                 int ci = host.lastIndexOf(':');
 250:                 if (ci != -1)
 251:                   {
 252:                     port = Integer.parseInt(host.substring (ci + 1));
 253:                     host = host.substring(0, ci);
 254:                   }
 255:                 else
 256:                   {
 257:                     port = HTTPConnection.HTTPS_PORT;
 258:                   }
 259:                 file = location.substring(end);
 260:                 retry = true;
 261:               }
 262:             // Otherwise this is not an HTTP redirect, can't follow
 263:           }
 264:         else
 265:           {
 266:             responseSink = new ByteArrayInputStream(reader.toByteArray ());
 267:           }
 268:       }
 269:     while (retry);
 270:     connected = true;
 271:   }
 272: 
 273:   /**
 274:    * Returns a connection, from the pool if necessary.
 275:    */
 276:   HTTPConnection getConnection(String host, int port, boolean secure)
 277:     throws IOException
 278:   {
 279:     HTTPConnection connection;
 280:     if (keepAlive)
 281:       {
 282:         StringBuffer buf = new StringBuffer(secure ? "https://" : "http://");
 283:         buf.append(host);
 284:         buf.append(':');
 285:         buf.append(port);
 286:         String key = buf.toString();
 287:         synchronized (connectionPool)
 288:           {
 289:             connection = (HTTPConnection) connectionPool.get(key);
 290:             if (connection == null)
 291:               {
 292:                 connection = new HTTPConnection(host, port, secure);
 293:                 // Good housekeeping
 294:                 if (connectionPool.size() == maxConnections)
 295:                   {
 296:                     // maxConnections must always be >= 1
 297:                     Object lru = connectionPool.keySet().iterator().next();
 298:                     connectionPool.remove(lru);
 299:                   }
 300:                 connectionPool.put(key, connection);
 301:               }
 302:           }
 303:       }
 304:     else
 305:       {
 306:         connection = new HTTPConnection(host, port, secure);
 307:       }
 308:     return connection;
 309:   }
 310: 
 311:   public void disconnect()
 312:   {
 313:     if (connection != null)
 314:       {
 315:         try
 316:           {
 317:             connection.close();
 318:           }
 319:         catch (IOException e)
 320:           {
 321:           }
 322:       }
 323:   }
 324: 
 325:   public boolean usingProxy()
 326:   {
 327:     return (proxyHostname != null);
 328:   }
 329: 
 330:   /**
 331:    * Overrides the corresponding method in HttpURLConnection to permit
 332:    * arbitrary methods, as long as they're valid ASCII alphabetic
 333:    * characters. This is to permit WebDAV and other HTTP extensions to
 334:    * function.
 335:    * @param method the method
 336:    */
 337:   public void setRequestMethod(String method)
 338:     throws ProtocolException
 339:   {
 340:     if (connected)
 341:       {
 342:         throw new ProtocolException("Already connected");
 343:       }
 344:     // Validate
 345:     method = method.toUpperCase();
 346:     int len = method.length();
 347:     if (len == 0)
 348:       {
 349:         throw new ProtocolException("Empty method name");
 350:       }
 351:     for (int i = 0; i < len; i++)
 352:       {
 353:         char c = method.charAt(i);
 354:         if (c < 0x41 || c > 0x5a)
 355:           {
 356:             throw new ProtocolException("Illegal character '" + c +
 357:                                         "' at index " + i);
 358:           }
 359:       }
 360:     // OK
 361:     this.method = method;
 362:     requestMethodSetExplicitly = true;
 363:   }
 364: 
 365:   public String getRequestProperty(String key)
 366:   {
 367:     return requestHeaders.getValue(key);
 368:   }
 369: 
 370:   public Map getRequestProperties()
 371:   {
 372:     return requestHeaders;
 373:   }
 374: 
 375:   public void setRequestProperty(String key, String value)
 376:   {
 377:     requestHeaders.put(key, value);
 378:   }
 379: 
 380:   public void addRequestProperty(String key, String value)
 381:   {
 382:     String old = requestHeaders.getValue(key);
 383:     if (old == null)
 384:       {
 385:         requestHeaders.put(key, value);
 386:       }
 387:     else
 388:       {
 389:         requestHeaders.put(key, old + "," + value);
 390:       }
 391:   }
 392: 
 393:   public OutputStream getOutputStream()
 394:     throws IOException
 395:   {
 396:     if (connected)
 397:       {
 398:         throw new ProtocolException("Already connected");
 399:       }
 400:     if (!doOutput)
 401:       {
 402:         throw new ProtocolException("doOutput is false");
 403:       }
 404:     else if (!requestMethodSetExplicitly)
 405:       {
 406:         /*
 407:          * Silently change the method to POST if no method was set
 408:          * explicitly. This is due to broken applications depending on this
 409:          * behaviour (Apache XMLRPC for one).
 410:          */
 411:         method = "POST";
 412:       }
 413:     if (requestSink == null)
 414:       {
 415:         requestSink = new ByteArrayOutputStream();
 416:       }
 417:     return requestSink;
 418:   }
 419:   
 420:   // -- Response --
 421:   
 422:   public InputStream getInputStream()
 423:     throws IOException
 424:   {
 425:     if (!connected)
 426:       {
 427:         connect();
 428:       }
 429:     if (!doInput)
 430:       {
 431:         throw new ProtocolException("doInput is false");
 432:       }
 433:     return responseSink;
 434:   }
 435: 
 436:   public Map getHeaderFields()
 437:   {
 438:     if (!connected)
 439:       {
 440:         try
 441:           {
 442:             connect();
 443:           }
 444:         catch (IOException e)
 445:           {
 446:             return null;
 447:           }
 448:       }
 449:     Map headers = response.getHeaders();
 450:     Map ret = new LinkedHashMap();
 451:     ret.put("", Collections.singletonList(getStatusLine(response)));
 452:     for (Iterator i = headers.entrySet().iterator(); i.hasNext(); )
 453:       {
 454:         Map.Entry entry = (Map.Entry) i.next();
 455:         String key = (String) entry.getKey();
 456:         String value = (String) entry.getValue();
 457:         ret.put(key, Collections.singletonList(value));
 458:       }
 459:     return ret;
 460:   }
 461: 
 462:   String getStatusLine(Response response)
 463:   {
 464:     return "HTTP/" + response.getMajorVersion() +
 465:       "." + response.getMinorVersion() +
 466:       " " + response.getCode() +
 467:       " " + response.getMessage();
 468:   }
 469:   
 470:   public String getHeaderField(int index)
 471:   {
 472:     if (!connected)
 473:       {
 474:         try
 475:           {
 476:             connect();
 477:           }
 478:         catch (IOException e)
 479:           {
 480:             return null;
 481:           }
 482:       }
 483:     if (index == 0)
 484:       {
 485:         return getStatusLine(response);
 486:       }
 487:     Iterator i = response.getHeaders().entrySet().iterator();
 488:     Map.Entry entry;
 489:     int count = 1;
 490:     do
 491:       {
 492:         if (!i.hasNext())
 493:           {
 494:             return null;
 495:           }
 496:         entry = (Map.Entry) i.next();
 497:         count++;
 498:       }
 499:     while (count <= index);
 500:     return (String) entry.getValue();
 501:   }
 502: 
 503:   public String getHeaderFieldKey(int index)
 504:   {
 505:     if (!connected)
 506:       {
 507:         try
 508:           {
 509:             connect();
 510:           }
 511:         catch (IOException e)
 512:           {
 513:             return null;
 514:           }
 515:       }
 516:     if (index == 0)
 517:       {
 518:         return null;
 519:       }
 520:     Iterator i = response.getHeaders().entrySet().iterator();
 521:     Map.Entry entry;
 522:     int count = 1;
 523:     do
 524:       {
 525:         entry = (Map.Entry) i.next();
 526:         count++;
 527:       }
 528:     while (count <= index);
 529:     return (String) entry.getKey();
 530:   }
 531: 
 532:   public String getHeaderField(String name)
 533:   {
 534:     if (!connected)
 535:       {
 536:         try
 537:           {
 538:             connect();
 539:           }
 540:         catch (IOException e)
 541:           {
 542:             return null;
 543:           }
 544:       }
 545:     return (String) response.getHeader(name);
 546:   }
 547: 
 548:   public long getHeaderFieldDate(String name, long def)
 549:   {
 550:     if (!connected)
 551:       {
 552:         try
 553:           {
 554:             connect();
 555:           }
 556:         catch (IOException e)
 557:           {
 558:             return def;
 559:           }
 560:       }
 561:     Date date = response.getDateHeader(name);
 562:     return (date == null) ? def : date.getTime();
 563:   }
 564: 
 565:   public String getContentType()
 566:   {
 567:     return getHeaderField("Content-Type");
 568:   }
 569: 
 570:   public int getResponseCode()
 571:     throws IOException
 572:   {
 573:     if (!connected)
 574:       {
 575:         connect();
 576:       }
 577:     return response.getCode();
 578:   }
 579: 
 580:   public String getResponseMessage()
 581:     throws IOException
 582:   {
 583:     if (!connected)
 584:       {
 585:         connect();
 586:       }
 587:     return response.getMessage();
 588:   }
 589: 
 590: }