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