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.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 specifed 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"); 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<Void>() { 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 = url.getFile(); 553 if ((fileName == null) || (fileName.length() == 0)) 554 fileName = "/"; 555 556 /** 557 * proxyDisabled is set by subclass HttpsClient! 558 */ 559 if (usingProxy && !proxyDisabled) { 560 // Do not use URLStreamHandler.toExternalForm as the fragment 561 // should not be part of the RequestURI. It should be an 562 // absolute URI which does not have a fragment part. 563 StringBuffer result = new StringBuffer(128); 564 result.append(url.getProtocol()); 565 result.append(":"); 566 if (url.getAuthority() != null && url.getAuthority().length() > 0) { 567 result.append("//"); 568 result.append(url.getAuthority()); 569 } 570 if (url.getPath() != null) { 571 result.append(url.getPath()); 572 } 573 if (url.getQuery() != null) { 574 result.append('?'); 575 result.append(url.getQuery()); 576 } 577 578 fileName = result.toString(); 579 } 580 if (fileName.indexOf('\n') == -1) 581 return fileName; 582 else 583 throw new java.net.MalformedURLException("Illegal character in URL"); 584 } 585 586 /** 587 * @deprecated 588 */ 589 @Deprecated 590 public void writeRequests(MessageHeader head) { 591 requests = head; 592 requests.print(serverOutput); 593 serverOutput.flush(); 594 } 595 596 public void writeRequests(MessageHeader head, 597 PosterOutputStream pos) throws IOException { 598 requests = head; 599 requests.print(serverOutput); 600 poster = pos; 601 if (poster != null) 602 poster.writeTo(serverOutput); 603 serverOutput.flush(); 604 } 605 606 public void writeRequests(MessageHeader head, 607 PosterOutputStream pos, 608 boolean streaming) throws IOException { 609 this.streaming = streaming; 610 writeRequests(head, pos); 611 } 612 613 /** Parse the first line of the HTTP request. It usually looks 614 something like: "HTTP/1.0 <number> comment\r\n". */ 615 616 public boolean parseHTTP(MessageHeader responses, ProgressSource pi, HttpURLConnection httpuc) 617 throws IOException { 618 /* If "HTTP/*" is found in the beginning, return true. Let 619 * HttpURLConnection parse the mime header itself. 620 * 621 * If this isn't valid HTTP, then we don't try to parse a header 622 * out of the beginning of the response into the responses, 623 * and instead just queue up the output stream to it's very beginning. 624 * This seems most reasonable, and is what the NN browser does. 625 */ 626 627 try { 628 serverInput = serverSocket.getInputStream(); 629 if (capture != null) { 630 serverInput = new HttpCaptureInputStream(serverInput, capture); 631 } 632 serverInput = new BufferedInputStream(serverInput); 633 return (parseHTTPHeader(responses, pi, httpuc)); 634 } catch (SocketTimeoutException stex) { 635 // We don't want to retry the request when the app. sets a timeout 636 // but don't close the server if timeout while waiting for 100-continue 637 if (ignoreContinue) { 638 closeServer(); 639 } 640 throw stex; 641 } catch (IOException e) { 642 closeServer(); 643 cachedHttpClient = false; 644 if (!failedOnce && requests != null) { 645 failedOnce = true; 646 if (getRequestMethod().equals("CONNECT") || 647 (httpuc.getRequestMethod().equals("POST") && 648 (!retryPostProp || streaming))) { 649 // do not retry the request 650 } else { 651 // try once more 652 openServer(); 653 if (needsTunneling()) { 654 httpuc.doTunneling(); 655 } 656 afterConnect(); 657 writeRequests(requests, poster); 658 return parseHTTP(responses, pi, httpuc); 659 } 660 } 661 throw e; 662 } 663 664 } 665 666 private boolean parseHTTPHeader(MessageHeader responses, ProgressSource pi, HttpURLConnection httpuc) 667 throws IOException { 668 /* If "HTTP/*" is found in the beginning, return true. Let 669 * HttpURLConnection parse the mime header itself. 670 * 671 * If this isn't valid HTTP, then we don't try to parse a header 672 * out of the beginning of the response into the responses, 673 * and instead just queue up the output stream to it's very beginning. 674 * This seems most reasonable, and is what the NN browser does. 675 */ 676 677 keepAliveConnections = -1; 678 keepAliveTimeout = 0; 679 680 boolean ret = false; 681 byte[] b = new byte[8]; 682 683 try { 684 int nread = 0; 685 serverInput.mark(10); 686 while (nread < 8) { 687 int r = serverInput.read(b, nread, 8 - nread); 688 if (r < 0) { 689 break; 690 } 691 nread += r; 692 } 693 String keep=null; 694 ret = b[0] == 'H' && b[1] == 'T' 695 && b[2] == 'T' && b[3] == 'P' && b[4] == '/' && 696 b[5] == '1' && b[6] == '.'; 697 serverInput.reset(); 698 if (ret) { // is valid HTTP - response started w/ "HTTP/1." 699 responses.parseHeader(serverInput); 700 701 // we've finished parsing http headers 702 // check if there are any applicable cookies to set (in cache) 703 CookieHandler cookieHandler = httpuc.getCookieHandler(); 704 if (cookieHandler != null) { 705 URI uri = ParseUtil.toURI(url); 706 // NOTE: That cast from Map shouldn't be necessary but 707 // a bug in javac is triggered under certain circumstances 708 // So we do put the cast in as a workaround until 709 // it is resolved. 710 if (uri != null) 711 cookieHandler.put(uri, responses.getHeaders()); 712 } 713 714 /* decide if we're keeping alive: 715 * This is a bit tricky. There's a spec, but most current 716 * servers (10/1/96) that support this differ in dialects. 717 * If the server/client misunderstand each other, the 718 * protocol should fall back onto HTTP/1.0, no keep-alive. 719 */ 720 if (usingProxy) { // not likely a proxy will return this 721 keep = responses.findValue("Proxy-Connection"); 722 } 723 if (keep == null) { 724 keep = responses.findValue("Connection"); 725 } 726 if (keep != null && keep.toLowerCase(Locale.US).equals("keep-alive")) { 727 /* some servers, notably Apache1.1, send something like: 728 * "Keep-Alive: timeout=15, max=1" which we should respect. 729 */ 730 HeaderParser p = new HeaderParser( 731 responses.findValue("Keep-Alive")); 732 if (p != null) { 733 /* default should be larger in case of proxy */ 734 keepAliveConnections = p.findInt("max", usingProxy?50:5); 735 keepAliveTimeout = p.findInt("timeout", usingProxy?60:5); 736 } 737 } else if (b[7] != '0') { 738 /* 739 * We're talking 1.1 or later. Keep persistent until 740 * the server says to close. 741 */ 742 if (keep != null) { 743 /* 744 * The only Connection token we understand is close. 745 * Paranoia: if there is any Connection header then 746 * treat as non-persistent. 747 */ 748 keepAliveConnections = 1; 749 } else { 750 keepAliveConnections = 5; 751 } 752 } 753 } else if (nread != 8) { 754 if (!failedOnce && requests != null) { 755 failedOnce = true; 756 if (getRequestMethod().equals("CONNECT") || 757 (httpuc.getRequestMethod().equals("POST") && 758 (!retryPostProp || streaming))) { 759 // do not retry the request 760 } else { 761 closeServer(); 762 cachedHttpClient = false; 763 openServer(); 764 if (needsTunneling()) { 765 httpuc.doTunneling(); 766 } 767 afterConnect(); 768 writeRequests(requests, poster); 769 return parseHTTP(responses, pi, httpuc); 770 } 771 } 772 throw new SocketException("Unexpected end of file from server"); 773 } else { 774 // we can't vouche for what this is.... 775 responses.set("Content-type", "unknown/unknown"); 776 } 777 } catch (IOException e) { 778 throw e; 779 } 780 781 int code = -1; 782 try { 783 String resp; 784 resp = responses.getValue(0); 785 /* should have no leading/trailing LWS 786 * expedite the typical case by assuming it has 787 * form "HTTP/1.x <WS> 2XX <mumble>" 788 */ 789 int ind; 790 ind = resp.indexOf(' '); 791 while(resp.charAt(ind) == ' ') 792 ind++; 793 code = Integer.parseInt(resp.substring(ind, ind + 3)); 794 } catch (Exception e) {} 795 796 if (code == HTTP_CONTINUE && ignoreContinue) { 797 responses.reset(); 798 return parseHTTPHeader(responses, pi, httpuc); 799 } 800 801 long cl = -1; 802 803 /* 804 * Set things up to parse the entity body of the reply. 805 * We should be smarter about avoid pointless work when 806 * the HTTP method and response code indicate there will be 807 * no entity body to parse. 808 */ 809 String te = responses.findValue("Transfer-Encoding"); 810 if (te != null && te.equalsIgnoreCase("chunked")) { 811 serverInput = new ChunkedInputStream(serverInput, this, responses); 812 813 /* 814 * If keep alive not specified then close after the stream 815 * has completed. 816 */ 817 if (keepAliveConnections <= 1) { 818 keepAliveConnections = 1; 819 keepingAlive = false; 820 } else { 821 keepingAlive = true; 822 } 823 failedOnce = false; 824 } else { 825 826 /* 827 * If it's a keep alive connection then we will keep 828 * (alive if :- 829 * 1. content-length is specified, or 830 * 2. "Not-Modified" or "No-Content" responses - RFC 2616 states that 831 * 204 or 304 response must not include a message body. 832 */ 833 String cls = responses.findValue("content-length"); 834 if (cls != null) { 835 try { 836 cl = Long.parseLong(cls); 837 } catch (NumberFormatException e) { 838 cl = -1; 839 } 840 } 841 String requestLine = requests.getKey(0); 842 843 if ((requestLine != null && 844 (requestLine.startsWith("HEAD"))) || 845 code == HttpURLConnection.HTTP_NOT_MODIFIED || 846 code == HttpURLConnection.HTTP_NO_CONTENT) { 847 cl = 0; 848 } 849 850 if (keepAliveConnections > 1 && 851 (cl >= 0 || 852 code == HttpURLConnection.HTTP_NOT_MODIFIED || 853 code == HttpURLConnection.HTTP_NO_CONTENT)) { 854 keepingAlive = true; 855 failedOnce = false; 856 } else if (keepingAlive) { 857 /* Previously we were keeping alive, and now we're not. Remove 858 * this from the cache (but only here, once) - otherwise we get 859 * multiple removes and the cache count gets messed up. 860 */ 861 keepingAlive=false; 862 } 863 } 864 865 /* wrap a KeepAliveStream/MeteredStream around it if appropriate */ 866 867 if (cl > 0) { 868 // In this case, content length is well known, so it is okay 869 // to wrap the input stream with KeepAliveStream/MeteredStream. 870 871 if (pi != null) { 872 // Progress monitor is enabled 873 pi.setContentType(responses.findValue("content-type")); 874 } 875 876 if (isKeepingAlive()) { 877 // Wrap KeepAliveStream if keep alive is enabled. 878 logFinest("KeepAlive stream used: " + url); 879 serverInput = new KeepAliveStream(serverInput, pi, cl, this); 880 failedOnce = false; 881 } 882 else { 883 serverInput = new MeteredStream(serverInput, pi, cl); 884 } 885 } 886 else if (cl == -1) { 887 // In this case, content length is unknown - the input 888 // stream would simply be a regular InputStream or 889 // ChunkedInputStream. 890 891 if (pi != null) { 892 // Progress monitoring is enabled. 893 894 pi.setContentType(responses.findValue("content-type")); 895 896 // Wrap MeteredStream for tracking indeterministic 897 // progress, even if the input stream is ChunkedInputStream. 898 serverInput = new MeteredStream(serverInput, pi, cl); 899 } 900 else { 901 // Progress monitoring is disabled, and there is no 902 // need to wrap an unknown length input stream. 903 904 // ** This is an no-op ** 905 } 906 } 907 else { 908 if (pi != null) 909 pi.finishTracking(); 910 } 911 912 return ret; 913 } 914 915 public synchronized InputStream getInputStream() { 916 return serverInput; 917 } 918 919 public OutputStream getOutputStream() { 920 return serverOutput; 921 } 922 923 @Override 924 public String toString() { 925 return getClass().getName()+"("+url+")"; 926 } 927 928 public final boolean isKeepingAlive() { 929 return getHttpKeepAliveSet() && keepingAlive; 930 } 931 932 public void setCacheRequest(CacheRequest cacheRequest) { 933 this.cacheRequest = cacheRequest; 934 } 935 936 CacheRequest getCacheRequest() { 937 return cacheRequest; 938 } 939 940 String getRequestMethod() { 941 if (requests != null) { 942 String requestLine = requests.getKey(0); 943 if (requestLine != null) { 944 return requestLine.split("\\s+")[0]; 945 } 946 } 947 return ""; 948 } 949 950 @Override 951 protected void finalize() throws Throwable { 952 // This should do nothing. The stream finalizer will 953 // close the fd. 954 } 955 956 public void setDoNotRetry(boolean value) { 957 // failedOnce is used to determine if a request should be retried. 958 failedOnce = value; 959 } 960 961 public void setIgnoreContinue(boolean value) { 962 ignoreContinue = value; 963 } 964 965 /* Use only on connections in error. */ 966 @Override 967 public void closeServer() { 968 try { 969 keepingAlive = false; 970 serverSocket.close(); 971 } catch (Exception e) {} 972 } 973 974 /** 975 * @return the proxy host being used for this client, or null 976 * if we're not going through a proxy 977 */ 978 public String getProxyHostUsed() { 979 if (!usingProxy) { 980 return null; 981 } else { 982 return ((InetSocketAddress)proxy.address()).getHostString(); 983 } 984 } 985 986 /** 987 * @return the proxy port being used for this client. Meaningless 988 * if getProxyHostUsed() gives null. 989 */ 990 public int getProxyPortUsed() { 991 if (usingProxy) 992 return ((InetSocketAddress)proxy.address()).getPort(); 993 return -1; 994 } 995 } 996