Path: blob/master/src/java.base/share/classes/sun/net/www/http/HttpClient.java
67771 views
/*1* Copyright (c) 1994, 2021, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package sun.net.www.http;2627import java.io.*;28import java.net.*;29import java.util.Locale;30import java.util.Objects;31import java.util.Properties;32import java.util.concurrent.locks.ReentrantLock;3334import sun.net.NetworkClient;35import sun.net.ProgressSource;36import sun.net.www.MessageHeader;37import sun.net.www.HeaderParser;38import sun.net.www.MeteredStream;39import sun.net.www.ParseUtil;40import sun.net.www.protocol.http.AuthenticatorKeys;41import sun.net.www.protocol.http.HttpURLConnection;42import sun.util.logging.PlatformLogger;43import static sun.net.www.protocol.http.HttpURLConnection.TunnelState.*;44import sun.security.action.GetPropertyAction;4546/**47* @author Herb Jellinek48* @author Dave Brown49*/50public class HttpClient extends NetworkClient {51private final ReentrantLock clientLock = new ReentrantLock();5253// whether this httpclient comes from the cache54protected boolean cachedHttpClient = false;5556protected boolean inCache;5758// Http requests we send59MessageHeader requests;6061// Http data we send with the headers62PosterOutputStream poster = null;6364// true if we are in streaming mode (fixed length or chunked)65boolean streaming;6667// if we've had one io error68boolean failedOnce = false;6970/** Response code for CONTINUE */71private boolean ignoreContinue = true;72private static final int HTTP_CONTINUE = 100;7374/** Default port number for http daemons. REMIND: make these private */75static final int httpPortNumber = 80;7677/** return default port number (subclasses may override) */78protected int getDefaultPort () { return httpPortNumber; }7980private static int getDefaultPort(String proto) {81if ("http".equalsIgnoreCase(proto))82return 80;83if ("https".equalsIgnoreCase(proto))84return 443;85return -1;86}8788/* All proxying (generic as well as instance-specific) may be89* disabled through use of this flag90*/91protected boolean proxyDisabled;9293// are we using proxy in this instance?94public boolean usingProxy = false;95// target host, port for the URL96protected String host;97protected int port;9899/* where we cache currently open, persistent connections */100protected static KeepAliveCache kac = new KeepAliveCache();101102private static boolean keepAliveProp = true;103104// retryPostProp is true by default so as to preserve behavior105// from previous releases.106private static boolean retryPostProp = true;107108/* Value of the system property jdk.ntlm.cache;109if false, then NTLM connections will not be cached.110The default value is 'true'. */111private static final boolean cacheNTLMProp;112/* Value of the system property jdk.spnego.cache;113if false, then connections authentified using the Negotiate/Kerberos114scheme will not be cached.115The default value is 'true'. */116private static final boolean cacheSPNEGOProp;117118volatile boolean keepingAlive; /* this is a keep-alive connection */119volatile boolean disableKeepAlive;/* keep-alive has been disabled for this120connection - this will be used when121recomputing the value of keepingAlive */122int keepAliveConnections = -1; /* number of keep-alives left */123124/*125* The timeout if specified by the server. Following values possible126* 0: the server specified no keep alive headers127* -1: the server provided "Connection: keep-alive" but did not specify a128* a particular time in a "Keep-Alive:" headers129* Positive values are the number of seconds specified by the server130* in a "Keep-Alive" header131*/132int keepAliveTimeout = 0;133134/** whether the response is to be cached */135private CacheRequest cacheRequest = null;136137/** Url being fetched. */138protected URL url;139140/* if set, the client will be reused and must not be put in cache */141public boolean reuse = false;142143// Traffic capture tool, if configured. See HttpCapture class for info144private HttpCapture capture = null;145146/* "jdk.https.negotiate.cbt" property can be set to "always" (always sent), "never" (never sent) or147* "domain:a,c.d,*.e.f" (sent to host a, or c.d or to the domain e.f and any of its subdomains). This is148* a comma separated list of arbitrary length with no white-space allowed.149* If enabled (for a particular destination) then Negotiate/SPNEGO authentication requests will include150* a channel binding token for the destination server. The default behavior and setting for the151* property is "never"152*/153private static final String spnegoCBT;154155private static final PlatformLogger logger = HttpURLConnection.getHttpLogger();156157private static void logFinest(String msg) {158if (logger.isLoggable(PlatformLogger.Level.FINEST)) {159logger.finest(msg);160}161}162private static void logError(String msg) {163if (logger.isLoggable(PlatformLogger.Level.SEVERE)) {164logger.severe(msg);165}166}167168protected volatile String authenticatorKey;169170/**171* A NOP method kept for backwards binary compatibility172* @deprecated -- system properties are no longer cached.173*/174@Deprecated175public static synchronized void resetProperties() {176}177178int getKeepAliveTimeout() {179return keepAliveTimeout;180}181182static String normalizeCBT(String s) {183if (s == null || s.equals("never")) {184return "never";185}186if (s.equals("always") || s.startsWith("domain:")) {187return s;188} else {189logError("Unexpected value for \"jdk.https.negotiate.cbt\" system property");190return "never";191}192}193194static {195Properties props = GetPropertyAction.privilegedGetProperties();196String keepAlive = props.getProperty("http.keepAlive");197String retryPost = props.getProperty("sun.net.http.retryPost");198String cacheNTLM = props.getProperty("jdk.ntlm.cache");199String cacheSPNEGO = props.getProperty("jdk.spnego.cache");200201String s = props.getProperty("jdk.https.negotiate.cbt");202spnegoCBT = normalizeCBT(s);203204if (keepAlive != null) {205keepAliveProp = Boolean.parseBoolean(keepAlive);206} else {207keepAliveProp = true;208}209210if (retryPost != null) {211retryPostProp = Boolean.parseBoolean(retryPost);212} else {213retryPostProp = true;214}215216if (cacheNTLM != null) {217cacheNTLMProp = Boolean.parseBoolean(cacheNTLM);218} else {219cacheNTLMProp = true;220}221222if (cacheSPNEGO != null) {223cacheSPNEGOProp = Boolean.parseBoolean(cacheSPNEGO);224} else {225cacheSPNEGOProp = true;226}227}228229/**230* @return true iff http keep alive is set (i.e. enabled). Defaults231* to true if the system property http.keepAlive isn't set.232*/233public boolean getHttpKeepAliveSet() {234return keepAliveProp;235}236237public String getSpnegoCBT() {238return spnegoCBT;239}240241protected HttpClient() {242}243244private HttpClient(URL url)245throws IOException {246this(url, (String)null, -1, false);247}248249protected HttpClient(URL url,250boolean proxyDisabled) throws IOException {251this(url, null, -1, proxyDisabled);252}253254/* This package-only CTOR should only be used for FTP piggy-backed on HTTP255* URL's that use this won't take advantage of keep-alive.256* Additionally, this constructor may be used as a last resort when the257* first HttpClient gotten through New() failed (probably b/c of a258* Keep-Alive mismatch).259*260* XXX That documentation is wrong ... it's not package-private any more261*/262public HttpClient(URL url, String proxyHost, int proxyPort)263throws IOException {264this(url, proxyHost, proxyPort, false);265}266267protected HttpClient(URL url, Proxy p, int to) throws IOException {268proxy = (p == null) ? Proxy.NO_PROXY : p;269this.host = url.getHost();270this.url = url;271port = url.getPort();272if (port == -1) {273port = getDefaultPort();274}275setConnectTimeout(to);276277capture = HttpCapture.getCapture(url);278openServer();279}280281protected static Proxy newHttpProxy(String proxyHost, int proxyPort,282String proto) {283if (proxyHost == null || proto == null)284return Proxy.NO_PROXY;285int pport = proxyPort < 0 ? getDefaultPort(proto) : proxyPort;286InetSocketAddress saddr = InetSocketAddress.createUnresolved(proxyHost, pport);287return new Proxy(Proxy.Type.HTTP, saddr);288}289290/*291* This constructor gives "ultimate" flexibility, including the ability292* to bypass implicit proxying. Sometimes we need to be using tunneling293* (transport or network level) instead of proxying (application level),294* for example when we don't want the application level data to become295* visible to third parties.296*297* @param url the URL to which we're connecting298* @param proxy proxy to use for this URL (e.g. forwarding)299* @param proxyPort proxy port to use for this URL300* @param proxyDisabled true to disable default proxying301*/302private HttpClient(URL url, String proxyHost, int proxyPort,303boolean proxyDisabled)304throws IOException {305this(url, proxyDisabled ? Proxy.NO_PROXY :306newHttpProxy(proxyHost, proxyPort, "http"), -1);307}308309public HttpClient(URL url, String proxyHost, int proxyPort,310boolean proxyDisabled, int to)311throws IOException {312this(url, proxyDisabled ? Proxy.NO_PROXY :313newHttpProxy(proxyHost, proxyPort, "http"), to);314}315316/* This class has no public constructor for HTTP. This method is used to317* get an HttpClient to the specified URL. If there's currently an318* active HttpClient to that server/port, you'll get that one.319*/320public static HttpClient New(URL url)321throws IOException {322return HttpClient.New(url, Proxy.NO_PROXY, -1, true, null);323}324325public static HttpClient New(URL url, boolean useCache)326throws IOException {327return HttpClient.New(url, Proxy.NO_PROXY, -1, useCache, null);328}329330public static HttpClient New(URL url, Proxy p, int to, boolean useCache,331HttpURLConnection httpuc) throws IOException332{333if (p == null) {334p = Proxy.NO_PROXY;335}336HttpClient ret = null;337/* see if one's already around */338if (useCache) {339ret = kac.get(url, null);340if (ret != null && httpuc != null &&341httpuc.streaming() &&342"POST".equals(httpuc.getRequestMethod())) {343if (!ret.available()) {344ret.inCache = false;345ret.closeServer();346ret = null;347}348}349if (ret != null) {350String ak = httpuc == null ? AuthenticatorKeys.DEFAULT351: httpuc.getAuthenticatorKey();352boolean compatible = Objects.equals(ret.proxy, p)353&& Objects.equals(ret.getAuthenticatorKey(), ak);354if (compatible) {355ret.lock();356try {357ret.cachedHttpClient = true;358assert ret.inCache;359ret.inCache = false;360if (httpuc != null && ret.needsTunneling())361httpuc.setTunnelState(TUNNELING);362logFinest("KeepAlive stream retrieved from the cache, " + ret);363} finally {364ret.unlock();365}366} else {367// We cannot return this connection to the cache as it's368// KeepAliveTimeout will get reset. We simply close the connection.369// This should be fine as it is very rare that a connection370// to the same host will not use the same proxy.371ret.lock();372try {373ret.inCache = false;374ret.closeServer();375} finally {376ret.unlock();377}378ret = null;379}380}381}382if (ret == null) {383ret = new HttpClient(url, p, to);384if (httpuc != null) {385ret.authenticatorKey = httpuc.getAuthenticatorKey();386}387} else {388@SuppressWarnings("removal")389SecurityManager security = System.getSecurityManager();390if (security != null) {391if (ret.proxy == Proxy.NO_PROXY || ret.proxy == null) {392security.checkConnect(InetAddress.getByName(url.getHost()).getHostAddress(), url.getPort());393} else {394security.checkConnect(url.getHost(), url.getPort());395}396}397ret.url = url;398}399return ret;400}401402public static HttpClient New(URL url, Proxy p, int to,403HttpURLConnection httpuc) throws IOException404{405return New(url, p, to, true, httpuc);406}407408public static HttpClient New(URL url, String proxyHost, int proxyPort,409boolean useCache)410throws IOException {411return New(url, newHttpProxy(proxyHost, proxyPort, "http"),412-1, useCache, null);413}414415public static HttpClient New(URL url, String proxyHost, int proxyPort,416boolean useCache, int to,417HttpURLConnection httpuc)418throws IOException {419return New(url, newHttpProxy(proxyHost, proxyPort, "http"),420to, useCache, httpuc);421}422423public final String getAuthenticatorKey() {424String k = authenticatorKey;425if (k == null) return AuthenticatorKeys.DEFAULT;426return k;427}428429/* return it to the cache as still usable, if:430* 1) It's keeping alive, AND431* 2) It still has some connections left, AND432* 3) It hasn't had a error (PrintStream.checkError())433* 4) It hasn't timed out434*435* If this client is not keepingAlive, it should have been436* removed from the cache in the parseHeaders() method.437*/438439public void finished() {440if (reuse) /* will be reused */441return;442keepAliveConnections--;443poster = null;444if (keepAliveConnections > 0 && isKeepingAlive() &&445!(serverOutput.checkError())) {446/* This connection is keepingAlive && still valid.447* Return it to the cache.448*/449putInKeepAliveCache();450} else {451closeServer();452}453}454455protected boolean available() {456boolean available = true;457int old = -1;458459lock();460try {461try {462old = serverSocket.getSoTimeout();463serverSocket.setSoTimeout(1);464BufferedInputStream tmpbuf =465new BufferedInputStream(serverSocket.getInputStream());466int r = tmpbuf.read();467if (r == -1) {468logFinest("HttpClient.available(): " +469"read returned -1: not available");470available = false;471}472} catch (SocketTimeoutException e) {473logFinest("HttpClient.available(): " +474"SocketTimeout: its available");475} finally {476if (old != -1)477serverSocket.setSoTimeout(old);478}479} catch (IOException e) {480logFinest("HttpClient.available(): " +481"SocketException: not available");482available = false;483} finally {484unlock();485}486return available;487}488489protected void putInKeepAliveCache() {490lock();491try {492if (inCache) {493assert false : "Duplicate put to keep alive cache";494return;495}496inCache = true;497kac.put(url, null, this);498} finally {499unlock();500}501}502503protected boolean isInKeepAliveCache() {504lock();505try {506return inCache;507} finally {508unlock();509}510}511512/*513* Close an idle connection to this URL (if it exists in the514* cache).515*/516public void closeIdleConnection() {517HttpClient http = kac.get(url, null);518if (http != null) {519http.closeServer();520}521}522523/* We're very particular here about what our InputStream to the server524* looks like for reasons that are apparent if you can decipher the525* method parseHTTP(). That's why this method is overidden from the526* superclass.527*/528@Override529public void openServer(String server, int port) throws IOException {530serverSocket = doConnect(server, port);531try {532OutputStream out = serverSocket.getOutputStream();533if (capture != null) {534out = new HttpCaptureOutputStream(out, capture);535}536serverOutput = new PrintStream(537new BufferedOutputStream(out),538false, encoding);539} catch (UnsupportedEncodingException e) {540throw new InternalError(encoding+" encoding not found", e);541}542serverSocket.setTcpNoDelay(true);543}544545/*546* Returns true if the http request should be tunneled through proxy.547* An example where this is the case is Https.548*/549public boolean needsTunneling() {550return false;551}552553/*554* Returns true if this httpclient is from cache555*/556public boolean isCachedConnection() {557lock();558try {559return cachedHttpClient;560} finally {561unlock();562}563}564565/*566* Finish any work left after the socket connection is567* established. In the normal http case, it's a NO-OP. Subclass568* may need to override this. An example is Https, where for569* direct connection to the origin server, ssl handshake needs to570* be done; for proxy tunneling, the socket needs to be converted571* into an SSL socket before ssl handshake can take place.572*/573public void afterConnect() throws IOException, UnknownHostException {574// NO-OP. Needs to be overwritten by HttpsClient575}576577/*578* call openServer in a privileged block579*/580@SuppressWarnings("removal")581private void privilegedOpenServer(final InetSocketAddress server)582throws IOException583{584assert clientLock.isHeldByCurrentThread();585try {586java.security.AccessController.doPrivileged(587new java.security.PrivilegedExceptionAction<>() {588public Void run() throws IOException {589openServer(server.getHostString(), server.getPort());590return null;591}592});593} catch (java.security.PrivilegedActionException pae) {594throw (IOException) pae.getException();595}596}597598/*599* call super.openServer600*/601private void superOpenServer(final String proxyHost,602final int proxyPort)603throws IOException, UnknownHostException604{605super.openServer(proxyHost, proxyPort);606}607608/*609*/610protected void openServer() throws IOException {611612@SuppressWarnings("removal")613SecurityManager security = System.getSecurityManager();614615lock();616try {617if (security != null) {618security.checkConnect(host, port);619}620621if (keepingAlive) { // already opened622return;623}624625if (url.getProtocol().equals("http") ||626url.getProtocol().equals("https")) {627628if ((proxy != null) && (proxy.type() == Proxy.Type.HTTP)) {629sun.net.www.URLConnection.setProxiedHost(host);630privilegedOpenServer((InetSocketAddress) proxy.address());631usingProxy = true;632return;633} else {634// make direct connection635openServer(host, port);636usingProxy = false;637return;638}639640} else {641/* we're opening some other kind of url, most likely an642* ftp url.643*/644if ((proxy != null) && (proxy.type() == Proxy.Type.HTTP)) {645sun.net.www.URLConnection.setProxiedHost(host);646privilegedOpenServer((InetSocketAddress) proxy.address());647usingProxy = true;648return;649} else {650// make direct connection651super.openServer(host, port);652usingProxy = false;653return;654}655}656} finally {657unlock();658}659}660661public String getURLFile() throws IOException {662663String fileName;664665/**666* proxyDisabled is set by subclass HttpsClient!667*/668if (usingProxy && !proxyDisabled) {669// Do not use URLStreamHandler.toExternalForm as the fragment670// should not be part of the RequestURI. It should be an671// absolute URI which does not have a fragment part.672StringBuilder result = new StringBuilder(128);673result.append(url.getProtocol());674result.append(":");675if (url.getAuthority() != null && !url.getAuthority().isEmpty()) {676result.append("//");677result.append(url.getAuthority());678}679if (url.getPath() != null) {680result.append(url.getPath());681}682if (url.getQuery() != null) {683result.append('?');684result.append(url.getQuery());685}686687fileName = result.toString();688} else {689fileName = url.getFile();690691if ((fileName == null) || (fileName.isEmpty())) {692fileName = "/";693} else if (fileName.charAt(0) == '?') {694/* HTTP/1.1 spec says in 5.1.2. about Request-URI:695* "Note that the absolute path cannot be empty; if696* none is present in the original URI, it MUST be697* given as "/" (the server root)." So if the file698* name here has only a query string, the path is699* empty and we also have to add a "/".700*/701fileName = "/" + fileName;702}703}704705if (fileName.indexOf('\n') == -1)706return fileName;707else708throw new java.net.MalformedURLException("Illegal character in URL");709}710711/**712* @deprecated713*/714@Deprecated715public void writeRequests(MessageHeader head) {716requests = head;717requests.print(serverOutput);718serverOutput.flush();719}720721public void writeRequests(MessageHeader head,722PosterOutputStream pos) throws IOException {723requests = head;724requests.print(serverOutput);725poster = pos;726if (poster != null)727poster.writeTo(serverOutput);728serverOutput.flush();729}730731public void writeRequests(MessageHeader head,732PosterOutputStream pos,733boolean streaming) throws IOException {734this.streaming = streaming;735writeRequests(head, pos);736}737738/** Parse the first line of the HTTP request. It usually looks739something like: {@literal "HTTP/1.0 <number> comment\r\n"}. */740741public boolean parseHTTP(MessageHeader responses, ProgressSource pi, HttpURLConnection httpuc)742throws IOException {743/* If "HTTP/*" is found in the beginning, return true. Let744* HttpURLConnection parse the mime header itself.745*746* If this isn't valid HTTP, then we don't try to parse a header747* out of the beginning of the response into the responses,748* and instead just queue up the output stream to it's very beginning.749* This seems most reasonable, and is what the NN browser does.750*/751752try {753serverInput = serverSocket.getInputStream();754if (capture != null) {755serverInput = new HttpCaptureInputStream(serverInput, capture);756}757serverInput = new BufferedInputStream(serverInput);758return (parseHTTPHeader(responses, pi, httpuc));759} catch (SocketTimeoutException stex) {760// We don't want to retry the request when the app. sets a timeout761// but don't close the server if timeout while waiting for 100-continue762if (ignoreContinue) {763closeServer();764}765throw stex;766} catch (IOException e) {767closeServer();768cachedHttpClient = false;769if (!failedOnce && requests != null) {770failedOnce = true;771if (getRequestMethod().equals("CONNECT")772|| streaming773|| (httpuc.getRequestMethod().equals("POST")774&& !retryPostProp)) {775// do not retry the request776} else {777// try once more778openServer();779checkTunneling(httpuc);780afterConnect();781writeRequests(requests, poster);782return parseHTTP(responses, pi, httpuc);783}784}785throw e;786}787788}789790// Check whether tunnel must be open and open it if necessary791// (in the case of HTTPS with proxy)792private void checkTunneling(HttpURLConnection httpuc) throws IOException {793if (needsTunneling()) {794MessageHeader origRequests = requests;795PosterOutputStream origPoster = poster;796httpuc.doTunneling();797requests = origRequests;798poster = origPoster;799}800}801802private boolean parseHTTPHeader(MessageHeader responses, ProgressSource pi, HttpURLConnection httpuc)803throws IOException {804/* If "HTTP/*" is found in the beginning, return true. Let805* HttpURLConnection parse the mime header itself.806*807* If this isn't valid HTTP, then we don't try to parse a header808* out of the beginning of the response into the responses,809* and instead just queue up the output stream to it's very beginning.810* This seems most reasonable, and is what the NN browser does.811*/812813keepAliveConnections = -1;814keepAliveTimeout = 0;815816boolean ret = false;817byte[] b = new byte[8];818819try {820int nread = 0;821serverInput.mark(10);822while (nread < 8) {823int r = serverInput.read(b, nread, 8 - nread);824if (r < 0) {825break;826}827nread += r;828}829String keep=null;830String authenticate=null;831ret = b[0] == 'H' && b[1] == 'T'832&& b[2] == 'T' && b[3] == 'P' && b[4] == '/' &&833b[5] == '1' && b[6] == '.';834serverInput.reset();835if (ret) { // is valid HTTP - response started w/ "HTTP/1."836responses.parseHeader(serverInput);837838// we've finished parsing http headers839// check if there are any applicable cookies to set (in cache)840CookieHandler cookieHandler = httpuc.getCookieHandler();841if (cookieHandler != null) {842URI uri = ParseUtil.toURI(url);843// NOTE: That cast from Map shouldn't be necessary but844// a bug in javac is triggered under certain circumstances845// So we do put the cast in as a workaround until846// it is resolved.847if (uri != null)848cookieHandler.put(uri, responses.getHeaders());849}850851/* decide if we're keeping alive:852* This is a bit tricky. There's a spec, but most current853* servers (10/1/96) that support this differ in dialects.854* If the server/client misunderstand each other, the855* protocol should fall back onto HTTP/1.0, no keep-alive.856*/857if (usingProxy) { // not likely a proxy will return this858keep = responses.findValue("Proxy-Connection");859authenticate = responses.findValue("Proxy-Authenticate");860}861if (keep == null) {862keep = responses.findValue("Connection");863authenticate = responses.findValue("WWW-Authenticate");864}865866// 'disableKeepAlive' starts with the value false.867// It can transition from false to true, but once true868// it stays true.869// If cacheNTLMProp is false, and disableKeepAlive is false,870// then we need to examine the response headers to figure out871// whether we are doing NTLM authentication. If we do NTLM,872// and cacheNTLMProp is false, than we can't keep this connection873// alive: we will switch disableKeepAlive to true.874boolean canKeepAlive = !disableKeepAlive;875if (canKeepAlive && (cacheNTLMProp == false || cacheSPNEGOProp == false)876&& authenticate != null) {877authenticate = authenticate.toLowerCase(Locale.US);878if (cacheNTLMProp == false) {879canKeepAlive &= !authenticate.startsWith("ntlm ");880}881if (cacheSPNEGOProp == false) {882canKeepAlive &= !authenticate.startsWith("negotiate ");883canKeepAlive &= !authenticate.startsWith("kerberos ");884}885}886disableKeepAlive |= !canKeepAlive;887888if (keep != null && keep.toLowerCase(Locale.US).equals("keep-alive")) {889/* some servers, notably Apache1.1, send something like:890* "Keep-Alive: timeout=15, max=1" which we should respect.891*/892if (disableKeepAlive) {893keepAliveConnections = 1;894} else {895HeaderParser p = new HeaderParser(896responses.findValue("Keep-Alive"));897/* default should be larger in case of proxy */898keepAliveConnections = p.findInt("max", usingProxy?50:5);899keepAliveTimeout = p.findInt("timeout", -1);900}901} else if (b[7] != '0') {902/*903* We're talking 1.1 or later. Keep persistent until904* the server says to close.905*/906if (keep != null || disableKeepAlive) {907/*908* The only Connection token we understand is close.909* Paranoia: if there is any Connection header then910* treat as non-persistent.911*/912keepAliveConnections = 1;913} else {914keepAliveConnections = 5;915}916}917} else if (nread != 8) {918if (!failedOnce && requests != null) {919failedOnce = true;920if (getRequestMethod().equals("CONNECT")921|| streaming922|| (httpuc.getRequestMethod().equals("POST")923&& !retryPostProp)) {924// do not retry the request925} else {926closeServer();927cachedHttpClient = false;928openServer();929checkTunneling(httpuc);930afterConnect();931writeRequests(requests, poster);932return parseHTTP(responses, pi, httpuc);933}934}935throw new SocketException("Unexpected end of file from server");936} else {937// we can't vouche for what this is....938responses.set("Content-type", "unknown/unknown");939}940} catch (IOException e) {941throw e;942}943944int code = -1;945try {946String resp;947resp = responses.getValue(0);948/* should have no leading/trailing LWS949* expedite the typical case by assuming it has950* form "HTTP/1.x <WS> 2XX <mumble>"951*/952int ind;953ind = resp.indexOf(' ');954while(resp.charAt(ind) == ' ')955ind++;956code = Integer.parseInt(resp, ind, ind + 3, 10);957} catch (Exception e) {}958959if (code == HTTP_CONTINUE && ignoreContinue) {960responses.reset();961return parseHTTPHeader(responses, pi, httpuc);962}963964long cl = -1;965966/*967* Set things up to parse the entity body of the reply.968* We should be smarter about avoid pointless work when969* the HTTP method and response code indicate there will be970* no entity body to parse.971*/972String te = responses.findValue("Transfer-Encoding");973if (te != null && te.equalsIgnoreCase("chunked")) {974serverInput = new ChunkedInputStream(serverInput, this, responses);975976/*977* If keep alive not specified then close after the stream978* has completed.979*/980if (keepAliveConnections <= 1) {981keepAliveConnections = 1;982keepingAlive = false;983} else {984keepingAlive = !disableKeepAlive;985}986failedOnce = false;987} else {988989/*990* If it's a keep alive connection then we will keep991* (alive if :-992* 1. content-length is specified, or993* 2. "Not-Modified" or "No-Content" responses - RFC 2616 states that994* 204 or 304 response must not include a message body.995*/996String cls = responses.findValue("content-length");997if (cls != null) {998try {999cl = Long.parseLong(cls);1000} catch (NumberFormatException e) {1001cl = -1;1002}1003}1004String requestLine = requests.getKey(0);10051006if ((requestLine != null &&1007(requestLine.startsWith("HEAD"))) ||1008code == HttpURLConnection.HTTP_NOT_MODIFIED ||1009code == HttpURLConnection.HTTP_NO_CONTENT) {1010cl = 0;1011}10121013if (keepAliveConnections > 1 &&1014(cl >= 0 ||1015code == HttpURLConnection.HTTP_NOT_MODIFIED ||1016code == HttpURLConnection.HTTP_NO_CONTENT)) {1017keepingAlive = !disableKeepAlive;1018failedOnce = false;1019} else if (keepingAlive) {1020/* Previously we were keeping alive, and now we're not. Remove1021* this from the cache (but only here, once) - otherwise we get1022* multiple removes and the cache count gets messed up.1023*/1024keepingAlive=false;1025}1026}10271028/* wrap a KeepAliveStream/MeteredStream around it if appropriate */10291030if (cl > 0) {1031// In this case, content length is well known, so it is okay1032// to wrap the input stream with KeepAliveStream/MeteredStream.10331034if (pi != null) {1035// Progress monitor is enabled1036pi.setContentType(responses.findValue("content-type"));1037}10381039// If disableKeepAlive == true, the client will not be returned1040// to the cache. But we still need to use a keepalive stream to1041// allow the multi-message authentication exchange on the connection1042boolean useKeepAliveStream = isKeepingAlive() || disableKeepAlive;1043if (useKeepAliveStream) {1044// Wrap KeepAliveStream if keep alive is enabled.1045logFinest("KeepAlive stream used: " + url);1046serverInput = new KeepAliveStream(serverInput, pi, cl, this);1047failedOnce = false;1048}1049else {1050serverInput = new MeteredStream(serverInput, pi, cl);1051}1052}1053else if (cl == -1) {1054// In this case, content length is unknown - the input1055// stream would simply be a regular InputStream or1056// ChunkedInputStream.10571058if (pi != null) {1059// Progress monitoring is enabled.10601061pi.setContentType(responses.findValue("content-type"));10621063// Wrap MeteredStream for tracking indeterministic1064// progress, even if the input stream is ChunkedInputStream.1065serverInput = new MeteredStream(serverInput, pi, cl);1066}1067else {1068// Progress monitoring is disabled, and there is no1069// need to wrap an unknown length input stream.10701071// ** This is an no-op **1072}1073}1074else {1075if (pi != null)1076pi.finishTracking();1077}10781079return ret;1080}10811082public InputStream getInputStream() {1083lock();1084try {1085return serverInput;1086} finally {1087unlock();1088}1089}10901091public OutputStream getOutputStream() {1092return serverOutput;1093}10941095@Override1096public String toString() {1097return getClass().getName()+"("+url+")";1098}10991100public final boolean isKeepingAlive() {1101return getHttpKeepAliveSet() && keepingAlive;1102}11031104public void setCacheRequest(CacheRequest cacheRequest) {1105this.cacheRequest = cacheRequest;1106}11071108CacheRequest getCacheRequest() {1109return cacheRequest;1110}11111112String getRequestMethod() {1113if (requests != null) {1114String requestLine = requests.getKey(0);1115if (requestLine != null) {1116return requestLine.split("\\s+")[0];1117}1118}1119return "";1120}11211122public void setDoNotRetry(boolean value) {1123// failedOnce is used to determine if a request should be retried.1124failedOnce = value;1125}11261127public void setIgnoreContinue(boolean value) {1128ignoreContinue = value;1129}11301131/* Use only on connections in error. */1132@Override1133public void closeServer() {1134try {1135keepingAlive = false;1136serverSocket.close();1137} catch (Exception e) {}1138}11391140/**1141* @return the proxy host being used for this client, or null1142* if we're not going through a proxy1143*/1144public String getProxyHostUsed() {1145if (!usingProxy) {1146return null;1147} else {1148return ((InetSocketAddress)proxy.address()).getHostString();1149}1150}11511152public boolean getUsingProxy() {1153return usingProxy;1154}11551156/**1157* @return the proxy port being used for this client. Meaningless1158* if getProxyHostUsed() gives null.1159*/1160public int getProxyPortUsed() {1161if (usingProxy)1162return ((InetSocketAddress)proxy.address()).getPort();1163return -1;1164}11651166public final void lock() {1167clientLock.lock();1168}11691170public final void unlock() {1171clientLock.unlock();1172}1173}117411751176