1 /* 2 * Copyright (c) 1994, 2010, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package sun.net.www.http; 27 28 import java.io; 29 import java.net; 30 import java.util.Locale; 31 import sun.net.NetworkClient; 32 import sun.net.ProgressSource; 33 import sun.net.www.MessageHeader; 34 import sun.net.www.HeaderParser; 35 import sun.net.www.MeteredStream; 36 import sun.net.www.ParseUtil; 37 import sun.net.www.protocol.http.HttpURLConnection; 38 import sun.util.logging.PlatformLogger; 39 40 /** 41 * @author Herb Jellinek 42 * @author Dave Brown 43 */ 44 public class HttpClient extends NetworkClient { 45 // whether this httpclient comes from the cache 46 protected boolean cachedHttpClient = false; 47 48 private boolean inCache; 49 50 protected CookieHandler cookieHandler; 51 52 // Http requests we send 53 MessageHeader requests; 54 55 // Http data we send with the headers 56 PosterOutputStream poster = null; 57 58 // true if we are in streaming mode (fixed length or chunked) 59 boolean streaming; 60 61 // if we've had one io error 62 boolean failedOnce = false; 63 64 /** Response code for CONTINUE */ 65 private boolean ignoreContinue = true; 66 private static final int HTTP_CONTINUE = 100; 67 68 /** Default port number for http daemons. REMIND: make these private */ 69 static final int httpPortNumber = 80; 70 71 /** return default port number (subclasses may override) */ 72 protected int getDefaultPort () { return httpPortNumber; } 73 74 static private int getDefaultPort(String proto) { 75 if ("http".equalsIgnoreCase(proto)) 76 return 80; 77 if ("https".equalsIgnoreCase(proto)) 78 return 443; 79 return -1; 80 } 81 82 /* All proxying (generic as well as instance-specific) may be 83 * disabled through use of this flag 84 */ 85 protected boolean proxyDisabled; 86 87 // are we using proxy in this instance? 88 public boolean usingProxy = false; 89 // target host, port for the URL 90 protected String host; 91 protected int port; 92 93 /* where we cache currently open, persistent connections */ 94 protected static KeepAliveCache kac = new KeepAliveCache(); 95 96 private static boolean keepAliveProp = true; 97 98 // retryPostProp is true by default so as to preserve behavior 99 // from previous releases. 100 private static boolean retryPostProp = true; 101 102 volatile boolean keepingAlive = false; /* this is a keep-alive connection */ 103 int keepAliveConnections = -1; /* number of keep-alives left */ 104 105 /**Idle timeout value, in milliseconds. Zero means infinity, 106 * iff keepingAlive=true. 107 * Unfortunately, we can't always believe this one. If I'm connected 108 * through a Netscape proxy to a server that sent me a keep-alive 109 * time of 15 sec, the proxy unilaterally terminates my connection 110 * after 5 sec. So we have to hard code our effective timeout to 111 * 4 sec for the case where we're using a proxy. *SIGH* 112 */ 113 int keepAliveTimeout = 0; 114 115 /** whether the response is to be cached */ 116 private CacheRequest cacheRequest = null; 117 118 /** Url being fetched. */ 119 protected URL url; 120 121 /* if set, the client will be reused and must not be put in cache */ 122 public boolean reuse = false; 123 124 // Traffic capture tool, if configured. See HttpCapture class for info 125 private HttpCapture capture = null; 126 127 /** 128 * A NOP method kept for backwards binary compatibility 129 * @deprecated -- system properties are no longer cached. 130 */ 131 @Deprecated 132 public static synchronized void resetProperties() { 133 } 134 135 int getKeepAliveTimeout() { 136 return keepAliveTimeout; 137 } 138 139 static { 140 String keepAlive = java.security.AccessController.doPrivileged( 141 new sun.security.action.GetPropertyAction("http.keepAlive")); 142 143 String retryPost = java.security.AccessController.doPrivileged( 144 new sun.security.action.GetPropertyAction("sun.net.http.retryPost")); 145 146 if (keepAlive != null) { 147 keepAliveProp = Boolean.valueOf(keepAlive).booleanValue(); 148 } else { 149 keepAliveProp = true; 150 } 151 152 if (retryPost != null) { 153 retryPostProp = Boolean.valueOf(retryPost).booleanValue(); 154 } else 155 retryPostProp = true; 156 157 } 158 159 /** 160 * @return true iff http keep alive is set (i.e. enabled). Defaults 161 * to true if the system property http.keepAlive isn't set. 162 */ 163 public boolean getHttpKeepAliveSet() { 164 return keepAliveProp; 165 } 166 167 168 protected HttpClient() { 169 } 170 171 private HttpClient(URL url) 172 throws IOException { 173 this(url, (String)null, -1, false); 174 } 175 176 protected HttpClient(URL url, 177 boolean proxyDisabled) throws IOException { 178 this(url, null, -1, proxyDisabled); 179 } 180 181 /* This package-only CTOR should only be used for FTP piggy-backed on HTTP 182 * HTTP URL's that use this won't take advantage of keep-alive. 183 * Additionally, this constructor may be used as a last resort when the 184 * first HttpClient gotten through New() failed (probably b/c of a 185 * Keep-Alive mismatch). 186 * 187 * XXX That documentation is wrong ... it's not package-private any more 188 */ 189 public HttpClient(URL url, String proxyHost, int proxyPort) 190 throws IOException { 191 this(url, proxyHost, proxyPort, false); 192 } 193 194 protected HttpClient(URL url, Proxy p, int to) throws IOException { 195 proxy = (p == null) ? Proxy.NO_PROXY : p; 196 this.host = url.getHost(); 197 this.url = url; 198 port = url.getPort(); 199 if (port == -1) { 200 port = getDefaultPort(); 201 } 202 setConnectTimeout(to); 203 204 // get the cookieHandler if there is any 205 cookieHandler = java.security.AccessController.doPrivileged( 206 new java.security.PrivilegedAction<CookieHandler>() { 207 public CookieHandler run() { 208 return CookieHandler.getDefault(); 209 } 210 }); 211 212 capture = HttpCapture.getCapture(url); 213 openServer(); 214 } 215 216 static protected Proxy newHttpProxy(String proxyHost, int proxyPort, 217 String proto) { 218 if (proxyHost == null || proto == null) 219 return Proxy.NO_PROXY; 220 int pport = proxyPort < 0 ? getDefaultPort(proto) : proxyPort; 221 InetSocketAddress saddr = InetSocketAddress.createUnresolved(proxyHost, pport); 222 return new Proxy(Proxy.Type.HTTP, saddr); 223 } 224 225 /* 226 * This constructor gives "ultimate" flexibility, including the ability 227 * to bypass implicit proxying. Sometimes we need to be using tunneling 228 * (transport or network level) instead of proxying (application level), 229 * for example when we don't want the application level data to become 230 * visible to third parties. 231 * 232 * @param url the URL to which we're connecting 233 * @param proxy proxy to use for this URL (e.g. forwarding) 234 * @param proxyPort proxy port to use for this URL 235 * @param proxyDisabled true to disable default proxying 236 */ 237 private HttpClient(URL url, String proxyHost, int proxyPort, 238 boolean proxyDisabled) 239 throws IOException { 240 this(url, proxyDisabled ? Proxy.NO_PROXY : 241 newHttpProxy(proxyHost, proxyPort, "http"), -1); 242 } 243 244 public HttpClient(URL url, String proxyHost, int proxyPort, 245 boolean proxyDisabled, int to) 246 throws IOException { 247 this(url, proxyDisabled ? Proxy.NO_PROXY : 248 newHttpProxy(proxyHost, proxyPort, "http"), to); 249 } 250 251 /* This class has no public constructor for HTTP. This method is used to 252 * get an HttpClient to the specifed URL. If there's currently an 253 * active HttpClient to that server/port, you'll get that one. 254 */ 255 public static HttpClient New(URL url) 256 throws IOException { 257 return HttpClient.New(url, Proxy.NO_PROXY, -1, true); 258 } 259 260 public static HttpClient New(URL url, boolean useCache) 261 throws IOException { 262 return HttpClient.New(url, Proxy.NO_PROXY, -1, useCache); 263 } 264 265 public static HttpClient New(URL url, Proxy p, int to, boolean useCache) 266 throws IOException { 267 if (p == null) { 268 p = Proxy.NO_PROXY; 269 } 270 HttpClient ret = null; 271 /* see if one's already around */ 272 if (useCache) { 273 ret = kac.get(url, null); 274 if (ret != null) { 275 if ((ret.proxy != null && ret.proxy.equals(p)) || 276 (ret.proxy == null && p == null)) { 277 synchronized (ret) { 278 ret.cachedHttpClient = true; 279 assert ret.inCache; 280 ret.inCache = false; 281 PlatformLogger logger = HttpURLConnection.getHttpLogger(); 282 if (logger.isLoggable(PlatformLogger.FINEST)) { 283 logger.finest("KeepAlive stream retrieved from the cache, " + ret); 284 } 285 } 286 } else { 287 // We cannot return this connection to the cache as it's 288 // KeepAliveTimeout will get reset. We simply close the connection. 289 // This should be fine as it is very rare that a connection 290 // to the same host will not use the same proxy. 291 synchronized(ret) { 292 ret.inCache = false; 293 ret.closeServer(); 294 } 295 ret = null; 296 } 297 } 298 } 299 if (ret == null) { 300 ret = new HttpClient(url, p, to); 301 } else { 302 SecurityManager security = System.getSecurityManager(); 303 if (security != null) { 304 if (ret.proxy == Proxy.NO_PROXY || ret.proxy == null) { 305 security.checkConnect(InetAddress.getByName(url.getHost()).getHostAddress(), url.getPort()); 306 } else { 307 security.checkConnect(url.getHost(), url.getPort()); 308 } 309 } 310 ret.url = url; 311 } 312 return ret; 313 } 314 315 public static HttpClient New(URL url, Proxy p, int to) throws IOException { 316 return New(url, p, to, true); 317 } 318 319 public static HttpClient New(URL url, String proxyHost, int proxyPort, 320 boolean useCache) 321 throws IOException { 322 return New(url, newHttpProxy(proxyHost, proxyPort, "http"), -1, useCache); 323 } 324 325 public static HttpClient New(URL url, String proxyHost, int proxyPort, 326 boolean useCache, int to) 327 throws IOException { 328 return New(url, newHttpProxy(proxyHost, proxyPort, "http"), to, useCache); 329 } 330 331 /* return it to the cache as still usable, if: 332 * 1) It's keeping alive, AND 333 * 2) It still has some connections left, AND 334 * 3) It hasn't had a error (PrintStream.checkError()) 335 * 4) It hasn't timed out 336 * 337 * If this client is not keepingAlive, it should have been 338 * removed from the cache in the parseHeaders() method. 339 */ 340 341 public void finished() { 342 if (reuse) /* will be reused */ 343 return; 344 keepAliveConnections--; 345 poster = null; 346 if (keepAliveConnections > 0 && isKeepingAlive() && 347 !(serverOutput.checkError())) { 348 /* This connection is keepingAlive && still valid. 349 * Return it to the cache. 350 */ 351 putInKeepAliveCache(); 352 } else { 353 closeServer(); 354 } 355 } 356 357 protected synchronized void putInKeepAliveCache() { 358 if (inCache) { 359 assert false : "Duplicate put to keep alive cache"; 360 return; 361 } 362 inCache = true; 363 kac.put(url, null, this); 364 } 365 366 protected synchronized boolean isInKeepAliveCache() { 367 return inCache; 368 } 369 370 /* 371 * Close an idle connection to this URL (if it exists in the 372 * cache). 373 */ 374 public void closeIdleConnection() { 375 HttpClient http = kac.get(url, null); 376 if (http != null) { 377 http.closeServer(); 378 } 379 } 380 381 /* We're very particular here about what our InputStream to the server 382 * looks like for reasons that are apparent if you can decipher the 383 * method parseHTTP(). That's why this method is overidden from the 384 * superclass. 385 */ 386 @Override 387 public void openServer(String server, int port) throws IOException { 388 serverSocket = doConnect(server, port); 389 try { 390 OutputStream out = serverSocket.getOutputStream(); 391 if (capture != null) { 392 out = new HttpCaptureOutputStream(out, capture); 393 } 394 serverOutput = new PrintStream( 395 new BufferedOutputStream(out), 396 false, encoding); 397 } catch (UnsupportedEncodingException e) { 398 throw new InternalError(encoding+" encoding not found"); 399 } 400 serverSocket.setTcpNoDelay(true); 401 } 402 403 /* 404 * Returns true if the http request should be tunneled through proxy. 405 * An example where this is the case is Https. 406 */ 407 public boolean needsTunneling() { 408 return false; 409 } 410 411 /* 412 * Returns true if this httpclient is from cache 413 */ 414 public synchronized boolean isCachedConnection() { 415 return cachedHttpClient; 416 } 417 418 /* 419 * Finish any work left after the socket connection is 420 * established. In the normal http case, it's a NO-OP. Subclass 421 * may need to override this. An example is Https, where for 422 * direct connection to the origin server, ssl handshake needs to 423 * be done; for proxy tunneling, the socket needs to be converted 424 * into an SSL socket before ssl handshake can take place. 425 */ 426 public void afterConnect() throws IOException, UnknownHostException { 427 // NO-OP. Needs to be overwritten by HttpsClient 428 } 429 430 /* 431 * call openServer in a privileged block 432 */ 433 private synchronized void privilegedOpenServer(final InetSocketAddress server) 434 throws IOException 435 { 436 try { 437 java.security.AccessController.doPrivileged( 438 new java.security.PrivilegedExceptionAction<Void>() { 439 public Void run() throws IOException { 440 openServer(server.getHostString(), server.getPort()); 441 return null; 442 } 443 }); 444 } catch (java.security.PrivilegedActionException pae) { 445 throw (IOException) pae.getException(); 446 } 447 } 448 449 /* 450 * call super.openServer 451 */ 452 private void superOpenServer(final String proxyHost, 453 final int proxyPort) 454 throws IOException, UnknownHostException 455 { 456 super.openServer(proxyHost, proxyPort); 457 } 458 459 /* 460 */ 461 protected synchronized void openServer() throws IOException { 462 463 SecurityManager security = System.getSecurityManager(); 464 465 if (security != null) { 466 security.checkConnect(host, port); 467 } 468 469 if (keepingAlive) { // already opened 470 return; 471 } 472 473 if (url.getProtocol().equals("http") || 474 url.getProtocol().equals("https") ) { 475 476 if ((proxy != null) && (proxy.type() == Proxy.Type.HTTP)) { 477 sun.net.www.URLConnection.setProxiedHost(host); 478 privilegedOpenServer((InetSocketAddress) proxy.address()); 479 usingProxy = true; 480 return; 481 } else { 482 // make direct connection 483 openServer(host, port); 484 usingProxy = false; 485 return; 486 } 487 488 } else { 489 /* we're opening some other kind of url, most likely an 490 * ftp url. 491 */ 492 if ((proxy != null) && (proxy.type() == Proxy.Type.HTTP)) { 493 sun.net.www.URLConnection.setProxiedHost(host); 494 privilegedOpenServer((InetSocketAddress) proxy.address()); 495 usingProxy = true; 496 return; 497 } else { 498 // make direct connection 499 super.openServer(host, port); 500 usingProxy = false; 501 return; 502 } 503 } 504 } 505 506 public String getURLFile() throws IOException { 507 508 String fileName = url.getFile(); 509 if ((fileName == null) || (fileName.length() == 0)) 510 fileName = "/"; 511 512 /** 513 * proxyDisabled is set by subclass HttpsClient! 514 */ 515 if (usingProxy && !proxyDisabled) { 516 // Do not use URLStreamHandler.toExternalForm as the fragment 517 // should not be part of the RequestURI. It should be an 518 // absolute URI which does not have a fragment part. 519 StringBuffer result = new StringBuffer(128); 520 result.append(url.getProtocol()); 521 result.append(":"); 522 if (url.getAuthority() != null && url.getAuthority().length() > 0) { 523 result.append("//"); 524 result.append(url.getAuthority()); 525 } 526 if (url.getPath() != null) { 527 result.append(url.getPath()); 528 } 529 if (url.getQuery() != null) { 530 result.append('?'); 531 result.append(url.getQuery()); 532 } 533 534 fileName = result.toString(); 535 } 536 if (fileName.indexOf('\n') == -1) 537 return fileName; 538 else 539 throw new java.net.MalformedURLException("Illegal character in URL"); 540 } 541 542 /** 543 * @deprecated 544 */ 545 @Deprecated 546 public void writeRequests(MessageHeader head) { 547 requests = head; 548 requests.print(serverOutput); 549 serverOutput.flush(); 550 } 551 552 public void writeRequests(MessageHeader head, 553 PosterOutputStream pos) throws IOException { 554 requests = head; 555 requests.print(serverOutput); 556 poster = pos; 557 if (poster != null) 558 poster.writeTo(serverOutput); 559 serverOutput.flush(); 560 } 561 562 public void writeRequests(MessageHeader head, 563 PosterOutputStream pos, 564 boolean streaming) throws IOException { 565 this.streaming = streaming; 566 writeRequests(head, pos); 567 } 568 569 /** Parse the first line of the HTTP request. It usually looks 570 something like: "HTTP/1.0 <number> comment\r\n". */ 571 572 public boolean parseHTTP(MessageHeader responses, ProgressSource pi, HttpURLConnection httpuc) 573 throws IOException { 574 /* If "HTTP/*" is found in the beginning, return true. Let 575 * HttpURLConnection parse the mime header itself. 576 * 577 * If this isn't valid HTTP, then we don't try to parse a header 578 * out of the beginning of the response into the responses, 579 * and instead just queue up the output stream to it's very beginning. 580 * This seems most reasonable, and is what the NN browser does. 581 */ 582 583 try { 584 serverInput = serverSocket.getInputStream(); 585 if (capture != null) { 586 serverInput = new HttpCaptureInputStream(serverInput, capture); 587 } 588 serverInput = new BufferedInputStream(serverInput); 589 return (parseHTTPHeader(responses, pi, httpuc)); 590 } catch (SocketTimeoutException stex) { 591 // We don't want to retry the request when the app. sets a timeout 592 // but don't close the server if timeout while waiting for 100-continue 593 if (ignoreContinue) { 594 closeServer(); 595 } 596 throw stex; 597 } catch (IOException e) { 598 closeServer(); 599 cachedHttpClient = false; 600 if (!failedOnce && requests != null) { 601 failedOnce = true; 602 if (httpuc.getRequestMethod().equals("POST") && (!retryPostProp || streaming)) { 603 // do not retry the request 604 } else { 605 // try once more 606 openServer(); 607 if (needsTunneling()) { 608 httpuc.doTunneling(); 609 } 610 afterConnect(); 611 writeRequests(requests, poster); 612 return parseHTTP(responses, pi, httpuc); 613 } 614 } 615 throw e; 616 } 617 618 } 619 620 private boolean parseHTTPHeader(MessageHeader responses, ProgressSource pi, HttpURLConnection httpuc) 621 throws IOException { 622 /* If "HTTP/*" is found in the beginning, return true. Let 623 * HttpURLConnection parse the mime header itself. 624 * 625 * If this isn't valid HTTP, then we don't try to parse a header 626 * out of the beginning of the response into the responses, 627 * and instead just queue up the output stream to it's very beginning. 628 * This seems most reasonable, and is what the NN browser does. 629 */ 630 631 keepAliveConnections = -1; 632 keepAliveTimeout = 0; 633 634 boolean ret = false; 635 byte[] b = new byte[8]; 636 637 try { 638 int nread = 0; 639 serverInput.mark(10); 640 while (nread < 8) { 641 int r = serverInput.read(b, nread, 8 - nread); 642 if (r < 0) { 643 break; 644 } 645 nread += r; 646 } 647 String keep=null; 648 ret = b[0] == 'H' && b[1] == 'T' 649 && b[2] == 'T' && b[3] == 'P' && b[4] == '/' && 650 b[5] == '1' && b[6] == '.'; 651 serverInput.reset(); 652 if (ret) { // is valid HTTP - response started w/ "HTTP/1." 653 responses.parseHeader(serverInput); 654 655 // we've finished parsing http headers 656 // check if there are any applicable cookies to set (in cache) 657 if (cookieHandler != null) { 658 URI uri = ParseUtil.toURI(url); 659 // NOTE: That cast from Map shouldn't be necessary but 660 // a bug in javac is triggered under certain circumstances 661 // So we do put the cast in as a workaround until 662 // it is resolved. 663 if (uri != null) 664 cookieHandler.put(uri, responses.getHeaders()); 665 } 666 667 /* decide if we're keeping alive: 668 * This is a bit tricky. There's a spec, but most current 669 * servers (10/1/96) that support this differ in dialects. 670 * If the server/client misunderstand each other, the 671 * protocol should fall back onto HTTP/1.0, no keep-alive. 672 */ 673 if (usingProxy) { // not likely a proxy will return this 674 keep = responses.findValue("Proxy-Connection"); 675 } 676 if (keep == null) { 677 keep = responses.findValue("Connection"); 678 } 679 if (keep != null && keep.toLowerCase(Locale.US).equals("keep-alive")) { 680 /* some servers, notably Apache1.1, send something like: 681 * "Keep-Alive: timeout=15, max=1" which we should respect. 682 */ 683 HeaderParser p = new HeaderParser( 684 responses.findValue("Keep-Alive")); 685 if (p != null) { 686 /* default should be larger in case of proxy */ 687 keepAliveConnections = p.findInt("max", usingProxy?50:5); 688 keepAliveTimeout = p.findInt("timeout", usingProxy?60:5); 689 } 690 } else if (b[7] != '0') { 691 /* 692 * We're talking 1.1 or later. Keep persistent until 693 * the server says to close. 694 */ 695 if (keep != null) { 696 /* 697 * The only Connection token we understand is close. 698 * Paranoia: if there is any Connection header then 699 * treat as non-persistent. 700 */ 701 keepAliveConnections = 1; 702 } else { 703 keepAliveConnections = 5; 704 } 705 } 706 } else if (nread != 8) { 707 if (!failedOnce && requests != null) { 708 failedOnce = true; 709 if (httpuc.getRequestMethod().equals("POST") && (!retryPostProp || streaming)) { 710 // do not retry the request 711 } else { 712 closeServer(); 713 cachedHttpClient = false; 714 openServer(); 715 if (needsTunneling()) { 716 httpuc.doTunneling(); 717 } 718 afterConnect(); 719 writeRequests(requests, poster); 720 return parseHTTP(responses, pi, httpuc); 721 } 722 } 723 throw new SocketException("Unexpected end of file from server"); 724 } else { 725 // we can't vouche for what this is.... 726 responses.set("Content-type", "unknown/unknown"); 727 } 728 } catch (IOException e) { 729 throw e; 730 } 731 732 int code = -1; 733 try { 734 String resp; 735 resp = responses.getValue(0); 736 /* should have no leading/trailing LWS 737 * expedite the typical case by assuming it has 738 * form "HTTP/1.x <WS> 2XX <mumble>" 739 */ 740 int ind; 741 ind = resp.indexOf(' '); 742 while(resp.charAt(ind) == ' ') 743 ind++; 744 code = Integer.parseInt(resp.substring(ind, ind + 3)); 745 } catch (Exception e) {} 746 747 if (code == HTTP_CONTINUE && ignoreContinue) { 748 responses.reset(); 749 return parseHTTPHeader(responses, pi, httpuc); 750 } 751 752 long cl = -1; 753 754 /* 755 * Set things up to parse the entity body of the reply. 756 * We should be smarter about avoid pointless work when 757 * the HTTP method and response code indicate there will be 758 * no entity body to parse. 759 */ 760 String te = responses.findValue("Transfer-Encoding"); 761 if (te != null && te.equalsIgnoreCase("chunked")) { 762 serverInput = new ChunkedInputStream(serverInput, this, responses); 763 764 /* 765 * If keep alive not specified then close after the stream 766 * has completed. 767 */ 768 if (keepAliveConnections <= 1) { 769 keepAliveConnections = 1; 770 keepingAlive = false; 771 } else { 772 keepingAlive = true; 773 } 774 failedOnce = false; 775 } else { 776 777 /* 778 * If it's a keep alive connection then we will keep 779 * (alive if :- 780 * 1. content-length is specified, or 781 * 2. "Not-Modified" or "No-Content" responses - RFC 2616 states that 782 * 204 or 304 response must not include a message body. 783 */ 784 String cls = responses.findValue("content-length"); 785 if (cls != null) { 786 try { 787 cl = Long.parseLong(cls); 788 } catch (NumberFormatException e) { 789 cl = -1; 790 } 791 } 792 String requestLine = requests.getKey(0); 793 794 if ((requestLine != null && 795 (requestLine.startsWith("HEAD"))) || 796 code == HttpURLConnection.HTTP_NOT_MODIFIED || 797 code == HttpURLConnection.HTTP_NO_CONTENT) { 798 cl = 0; 799 } 800 801 if (keepAliveConnections > 1 && 802 (cl >= 0 || 803 code == HttpURLConnection.HTTP_NOT_MODIFIED || 804 code == HttpURLConnection.HTTP_NO_CONTENT)) { 805 keepingAlive = true; 806 failedOnce = false; 807 } else if (keepingAlive) { 808 /* Previously we were keeping alive, and now we're not. Remove 809 * this from the cache (but only here, once) - otherwise we get 810 * multiple removes and the cache count gets messed up. 811 */ 812 keepingAlive=false; 813 } 814 } 815 816 /* wrap a KeepAliveStream/MeteredStream around it if appropriate */ 817 818 if (cl > 0) { 819 // In this case, content length is well known, so it is okay 820 // to wrap the input stream with KeepAliveStream/MeteredStream. 821 822 if (pi != null) { 823 // Progress monitor is enabled 824 pi.setContentType(responses.findValue("content-type")); 825 } 826 827 if (isKeepingAlive()) { 828 // Wrap KeepAliveStream if keep alive is enabled. 829 PlatformLogger logger = HttpURLConnection.getHttpLogger(); 830 if (logger.isLoggable(PlatformLogger.FINEST)) { 831 logger.finest("KeepAlive stream used: " + url); 832 } 833 serverInput = new KeepAliveStream(serverInput, pi, cl, this); 834 failedOnce = false; 835 } 836 else { 837 serverInput = new MeteredStream(serverInput, pi, cl); 838 } 839 } 840 else if (cl == -1) { 841 // In this case, content length is unknown - the input 842 // stream would simply be a regular InputStream or 843 // ChunkedInputStream. 844 845 if (pi != null) { 846 // Progress monitoring is enabled. 847 848 pi.setContentType(responses.findValue("content-type")); 849 850 // Wrap MeteredStream for tracking indeterministic 851 // progress, even if the input stream is ChunkedInputStream. 852 serverInput = new MeteredStream(serverInput, pi, cl); 853 } 854 else { 855 // Progress monitoring is disabled, and there is no 856 // need to wrap an unknown length input stream. 857 858 // ** This is an no-op ** 859 } 860 } 861 else { 862 if (pi != null) 863 pi.finishTracking(); 864 } 865 866 return ret; 867 } 868 869 public synchronized InputStream getInputStream() { 870 return serverInput; 871 } 872 873 public OutputStream getOutputStream() { 874 return serverOutput; 875 } 876 877 @Override 878 public String toString() { 879 return getClass().getName()+"("+url+")"; 880 } 881 882 public final boolean isKeepingAlive() { 883 return getHttpKeepAliveSet() && keepingAlive; 884 } 885 886 public void setCacheRequest(CacheRequest cacheRequest) { 887 this.cacheRequest = cacheRequest; 888 } 889 890 CacheRequest getCacheRequest() { 891 return cacheRequest; 892 } 893 894 @Override 895 protected void finalize() throws Throwable { 896 // This should do nothing. The stream finalizer will 897 // close the fd. 898 } 899 900 public void setDoNotRetry(boolean value) { 901 // failedOnce is used to determine if a request should be retried. 902 failedOnce = value; 903 } 904 905 public void setIgnoreContinue(boolean value) { 906 ignoreContinue = value; 907 } 908 909 /* Use only on connections in error. */ 910 @Override 911 public void closeServer() { 912 try { 913 keepingAlive = false; 914 serverSocket.close(); 915 } catch (Exception e) {} 916 } 917 918 /** 919 * @return the proxy host being used for this client, or null 920 * if we're not going through a proxy 921 */ 922 public String getProxyHostUsed() { 923 if (!usingProxy) { 924 return null; 925 } else { 926 return ((InetSocketAddress)proxy.address()).getHostString(); 927 } 928 } 929 930 /** 931 * @return the proxy port being used for this client. Meaningless 932 * if getProxyHostUsed() gives null. 933 */ 934 public int getProxyPortUsed() { 935 if (usingProxy) 936 return ((InetSocketAddress)proxy.address()).getPort(); 937 return -1; 938 } 939 }