Path: blob/master/src/java.base/share/classes/sun/net/www/http/HttpClient.java
41161 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/**Idle timeout value, in milliseconds. Zero means infinity,125* iff keepingAlive=true.126* Unfortunately, we can't always believe this one. If I'm connected127* through a Netscape proxy to a server that sent me a keep-alive128* time of 15 sec, the proxy unilaterally terminates my connection129* after 5 sec. So we have to hard code our effective timeout to130* 4 sec for the case where we're using a proxy. *SIGH*131*/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;145146private static final PlatformLogger logger = HttpURLConnection.getHttpLogger();147private static void logFinest(String msg) {148if (logger.isLoggable(PlatformLogger.Level.FINEST)) {149logger.finest(msg);150}151}152153protected volatile String authenticatorKey;154155/**156* A NOP method kept for backwards binary compatibility157* @deprecated -- system properties are no longer cached.158*/159@Deprecated160public static synchronized void resetProperties() {161}162163int getKeepAliveTimeout() {164return keepAliveTimeout;165}166167static {168Properties props = GetPropertyAction.privilegedGetProperties();169String keepAlive = props.getProperty("http.keepAlive");170String retryPost = props.getProperty("sun.net.http.retryPost");171String cacheNTLM = props.getProperty("jdk.ntlm.cache");172String cacheSPNEGO = props.getProperty("jdk.spnego.cache");173174if (keepAlive != null) {175keepAliveProp = Boolean.parseBoolean(keepAlive);176} else {177keepAliveProp = true;178}179180if (retryPost != null) {181retryPostProp = Boolean.parseBoolean(retryPost);182} else {183retryPostProp = true;184}185186if (cacheNTLM != null) {187cacheNTLMProp = Boolean.parseBoolean(cacheNTLM);188} else {189cacheNTLMProp = true;190}191192if (cacheSPNEGO != null) {193cacheSPNEGOProp = Boolean.parseBoolean(cacheSPNEGO);194} else {195cacheSPNEGOProp = true;196}197}198199/**200* @return true iff http keep alive is set (i.e. enabled). Defaults201* to true if the system property http.keepAlive isn't set.202*/203public boolean getHttpKeepAliveSet() {204return keepAliveProp;205}206207208protected HttpClient() {209}210211private HttpClient(URL url)212throws IOException {213this(url, (String)null, -1, false);214}215216protected HttpClient(URL url,217boolean proxyDisabled) throws IOException {218this(url, null, -1, proxyDisabled);219}220221/* This package-only CTOR should only be used for FTP piggy-backed on HTTP222* URL's that use this won't take advantage of keep-alive.223* Additionally, this constructor may be used as a last resort when the224* first HttpClient gotten through New() failed (probably b/c of a225* Keep-Alive mismatch).226*227* XXX That documentation is wrong ... it's not package-private any more228*/229public HttpClient(URL url, String proxyHost, int proxyPort)230throws IOException {231this(url, proxyHost, proxyPort, false);232}233234protected HttpClient(URL url, Proxy p, int to) throws IOException {235proxy = (p == null) ? Proxy.NO_PROXY : p;236this.host = url.getHost();237this.url = url;238port = url.getPort();239if (port == -1) {240port = getDefaultPort();241}242setConnectTimeout(to);243244capture = HttpCapture.getCapture(url);245openServer();246}247248protected static Proxy newHttpProxy(String proxyHost, int proxyPort,249String proto) {250if (proxyHost == null || proto == null)251return Proxy.NO_PROXY;252int pport = proxyPort < 0 ? getDefaultPort(proto) : proxyPort;253InetSocketAddress saddr = InetSocketAddress.createUnresolved(proxyHost, pport);254return new Proxy(Proxy.Type.HTTP, saddr);255}256257/*258* This constructor gives "ultimate" flexibility, including the ability259* to bypass implicit proxying. Sometimes we need to be using tunneling260* (transport or network level) instead of proxying (application level),261* for example when we don't want the application level data to become262* visible to third parties.263*264* @param url the URL to which we're connecting265* @param proxy proxy to use for this URL (e.g. forwarding)266* @param proxyPort proxy port to use for this URL267* @param proxyDisabled true to disable default proxying268*/269private HttpClient(URL url, String proxyHost, int proxyPort,270boolean proxyDisabled)271throws IOException {272this(url, proxyDisabled ? Proxy.NO_PROXY :273newHttpProxy(proxyHost, proxyPort, "http"), -1);274}275276public HttpClient(URL url, String proxyHost, int proxyPort,277boolean proxyDisabled, int to)278throws IOException {279this(url, proxyDisabled ? Proxy.NO_PROXY :280newHttpProxy(proxyHost, proxyPort, "http"), to);281}282283/* This class has no public constructor for HTTP. This method is used to284* get an HttpClient to the specified URL. If there's currently an285* active HttpClient to that server/port, you'll get that one.286*/287public static HttpClient New(URL url)288throws IOException {289return HttpClient.New(url, Proxy.NO_PROXY, -1, true, null);290}291292public static HttpClient New(URL url, boolean useCache)293throws IOException {294return HttpClient.New(url, Proxy.NO_PROXY, -1, useCache, null);295}296297public static HttpClient New(URL url, Proxy p, int to, boolean useCache,298HttpURLConnection httpuc) throws IOException299{300if (p == null) {301p = Proxy.NO_PROXY;302}303HttpClient ret = null;304/* see if one's already around */305if (useCache) {306ret = kac.get(url, null);307if (ret != null && httpuc != null &&308httpuc.streaming() &&309httpuc.getRequestMethod() == "POST") {310if (!ret.available()) {311ret.inCache = false;312ret.closeServer();313ret = null;314}315}316if (ret != null) {317String ak = httpuc == null ? AuthenticatorKeys.DEFAULT318: httpuc.getAuthenticatorKey();319boolean compatible = Objects.equals(ret.proxy, p)320&& Objects.equals(ret.getAuthenticatorKey(), ak);321if (compatible) {322ret.lock();323try {324ret.cachedHttpClient = true;325assert ret.inCache;326ret.inCache = false;327if (httpuc != null && ret.needsTunneling())328httpuc.setTunnelState(TUNNELING);329logFinest("KeepAlive stream retrieved from the cache, " + ret);330} finally {331ret.unlock();332}333} else {334// We cannot return this connection to the cache as it's335// KeepAliveTimeout will get reset. We simply close the connection.336// This should be fine as it is very rare that a connection337// to the same host will not use the same proxy.338ret.lock();339try {340ret.inCache = false;341ret.closeServer();342} finally {343ret.unlock();344}345ret = null;346}347}348}349if (ret == null) {350ret = new HttpClient(url, p, to);351if (httpuc != null) {352ret.authenticatorKey = httpuc.getAuthenticatorKey();353}354} else {355@SuppressWarnings("removal")356SecurityManager security = System.getSecurityManager();357if (security != null) {358if (ret.proxy == Proxy.NO_PROXY || ret.proxy == null) {359security.checkConnect(InetAddress.getByName(url.getHost()).getHostAddress(), url.getPort());360} else {361security.checkConnect(url.getHost(), url.getPort());362}363}364ret.url = url;365}366return ret;367}368369public static HttpClient New(URL url, Proxy p, int to,370HttpURLConnection httpuc) throws IOException371{372return New(url, p, to, true, httpuc);373}374375public static HttpClient New(URL url, String proxyHost, int proxyPort,376boolean useCache)377throws IOException {378return New(url, newHttpProxy(proxyHost, proxyPort, "http"),379-1, useCache, null);380}381382public static HttpClient New(URL url, String proxyHost, int proxyPort,383boolean useCache, int to,384HttpURLConnection httpuc)385throws IOException {386return New(url, newHttpProxy(proxyHost, proxyPort, "http"),387to, useCache, httpuc);388}389390public final String getAuthenticatorKey() {391String k = authenticatorKey;392if (k == null) return AuthenticatorKeys.DEFAULT;393return k;394}395396/* return it to the cache as still usable, if:397* 1) It's keeping alive, AND398* 2) It still has some connections left, AND399* 3) It hasn't had a error (PrintStream.checkError())400* 4) It hasn't timed out401*402* If this client is not keepingAlive, it should have been403* removed from the cache in the parseHeaders() method.404*/405406public void finished() {407if (reuse) /* will be reused */408return;409keepAliveConnections--;410poster = null;411if (keepAliveConnections > 0 && isKeepingAlive() &&412!(serverOutput.checkError())) {413/* This connection is keepingAlive && still valid.414* Return it to the cache.415*/416putInKeepAliveCache();417} else {418closeServer();419}420}421422protected boolean available() {423boolean available = true;424int old = -1;425426lock();427try {428try {429old = serverSocket.getSoTimeout();430serverSocket.setSoTimeout(1);431BufferedInputStream tmpbuf =432new BufferedInputStream(serverSocket.getInputStream());433int r = tmpbuf.read();434if (r == -1) {435logFinest("HttpClient.available(): " +436"read returned -1: not available");437available = false;438}439} catch (SocketTimeoutException e) {440logFinest("HttpClient.available(): " +441"SocketTimeout: its available");442} finally {443if (old != -1)444serverSocket.setSoTimeout(old);445}446} catch (IOException e) {447logFinest("HttpClient.available(): " +448"SocketException: not available");449available = false;450} finally {451unlock();452}453return available;454}455456protected void putInKeepAliveCache() {457lock();458try {459if (inCache) {460assert false : "Duplicate put to keep alive cache";461return;462}463inCache = true;464kac.put(url, null, this);465} finally {466unlock();467}468}469470protected boolean isInKeepAliveCache() {471lock();472try {473return inCache;474} finally {475unlock();476}477}478479/*480* Close an idle connection to this URL (if it exists in the481* cache).482*/483public void closeIdleConnection() {484HttpClient http = kac.get(url, null);485if (http != null) {486http.closeServer();487}488}489490/* We're very particular here about what our InputStream to the server491* looks like for reasons that are apparent if you can decipher the492* method parseHTTP(). That's why this method is overidden from the493* superclass.494*/495@Override496public void openServer(String server, int port) throws IOException {497serverSocket = doConnect(server, port);498try {499OutputStream out = serverSocket.getOutputStream();500if (capture != null) {501out = new HttpCaptureOutputStream(out, capture);502}503serverOutput = new PrintStream(504new BufferedOutputStream(out),505false, encoding);506} catch (UnsupportedEncodingException e) {507throw new InternalError(encoding+" encoding not found", e);508}509serverSocket.setTcpNoDelay(true);510}511512/*513* Returns true if the http request should be tunneled through proxy.514* An example where this is the case is Https.515*/516public boolean needsTunneling() {517return false;518}519520/*521* Returns true if this httpclient is from cache522*/523public boolean isCachedConnection() {524lock();525try {526return cachedHttpClient;527} finally {528unlock();529}530}531532/*533* Finish any work left after the socket connection is534* established. In the normal http case, it's a NO-OP. Subclass535* may need to override this. An example is Https, where for536* direct connection to the origin server, ssl handshake needs to537* be done; for proxy tunneling, the socket needs to be converted538* into an SSL socket before ssl handshake can take place.539*/540public void afterConnect() throws IOException, UnknownHostException {541// NO-OP. Needs to be overwritten by HttpsClient542}543544/*545* call openServer in a privileged block546*/547@SuppressWarnings("removal")548private void privilegedOpenServer(final InetSocketAddress server)549throws IOException550{551assert clientLock.isHeldByCurrentThread();552try {553java.security.AccessController.doPrivileged(554new java.security.PrivilegedExceptionAction<>() {555public Void run() throws IOException {556openServer(server.getHostString(), server.getPort());557return null;558}559});560} catch (java.security.PrivilegedActionException pae) {561throw (IOException) pae.getException();562}563}564565/*566* call super.openServer567*/568private void superOpenServer(final String proxyHost,569final int proxyPort)570throws IOException, UnknownHostException571{572super.openServer(proxyHost, proxyPort);573}574575/*576*/577protected void openServer() throws IOException {578579@SuppressWarnings("removal")580SecurityManager security = System.getSecurityManager();581582lock();583try {584if (security != null) {585security.checkConnect(host, port);586}587588if (keepingAlive) { // already opened589return;590}591592if (url.getProtocol().equals("http") ||593url.getProtocol().equals("https")) {594595if ((proxy != null) && (proxy.type() == Proxy.Type.HTTP)) {596sun.net.www.URLConnection.setProxiedHost(host);597privilegedOpenServer((InetSocketAddress) proxy.address());598usingProxy = true;599return;600} else {601// make direct connection602openServer(host, port);603usingProxy = false;604return;605}606607} else {608/* we're opening some other kind of url, most likely an609* ftp url.610*/611if ((proxy != null) && (proxy.type() == Proxy.Type.HTTP)) {612sun.net.www.URLConnection.setProxiedHost(host);613privilegedOpenServer((InetSocketAddress) proxy.address());614usingProxy = true;615return;616} else {617// make direct connection618super.openServer(host, port);619usingProxy = false;620return;621}622}623} finally {624unlock();625}626}627628public String getURLFile() throws IOException {629630String fileName;631632/**633* proxyDisabled is set by subclass HttpsClient!634*/635if (usingProxy && !proxyDisabled) {636// Do not use URLStreamHandler.toExternalForm as the fragment637// should not be part of the RequestURI. It should be an638// absolute URI which does not have a fragment part.639StringBuilder result = new StringBuilder(128);640result.append(url.getProtocol());641result.append(":");642if (url.getAuthority() != null && !url.getAuthority().isEmpty()) {643result.append("//");644result.append(url.getAuthority());645}646if (url.getPath() != null) {647result.append(url.getPath());648}649if (url.getQuery() != null) {650result.append('?');651result.append(url.getQuery());652}653654fileName = result.toString();655} else {656fileName = url.getFile();657658if ((fileName == null) || (fileName.isEmpty())) {659fileName = "/";660} else if (fileName.charAt(0) == '?') {661/* HTTP/1.1 spec says in 5.1.2. about Request-URI:662* "Note that the absolute path cannot be empty; if663* none is present in the original URI, it MUST be664* given as "/" (the server root)." So if the file665* name here has only a query string, the path is666* empty and we also have to add a "/".667*/668fileName = "/" + fileName;669}670}671672if (fileName.indexOf('\n') == -1)673return fileName;674else675throw new java.net.MalformedURLException("Illegal character in URL");676}677678/**679* @deprecated680*/681@Deprecated682public void writeRequests(MessageHeader head) {683requests = head;684requests.print(serverOutput);685serverOutput.flush();686}687688public void writeRequests(MessageHeader head,689PosterOutputStream pos) throws IOException {690requests = head;691requests.print(serverOutput);692poster = pos;693if (poster != null)694poster.writeTo(serverOutput);695serverOutput.flush();696}697698public void writeRequests(MessageHeader head,699PosterOutputStream pos,700boolean streaming) throws IOException {701this.streaming = streaming;702writeRequests(head, pos);703}704705/** Parse the first line of the HTTP request. It usually looks706something like: {@literal "HTTP/1.0 <number> comment\r\n"}. */707708public boolean parseHTTP(MessageHeader responses, ProgressSource pi, HttpURLConnection httpuc)709throws IOException {710/* If "HTTP/*" is found in the beginning, return true. Let711* HttpURLConnection parse the mime header itself.712*713* If this isn't valid HTTP, then we don't try to parse a header714* out of the beginning of the response into the responses,715* and instead just queue up the output stream to it's very beginning.716* This seems most reasonable, and is what the NN browser does.717*/718719try {720serverInput = serverSocket.getInputStream();721if (capture != null) {722serverInput = new HttpCaptureInputStream(serverInput, capture);723}724serverInput = new BufferedInputStream(serverInput);725return (parseHTTPHeader(responses, pi, httpuc));726} catch (SocketTimeoutException stex) {727// We don't want to retry the request when the app. sets a timeout728// but don't close the server if timeout while waiting for 100-continue729if (ignoreContinue) {730closeServer();731}732throw stex;733} catch (IOException e) {734closeServer();735cachedHttpClient = false;736if (!failedOnce && requests != null) {737failedOnce = true;738if (getRequestMethod().equals("CONNECT")739|| streaming740|| (httpuc.getRequestMethod().equals("POST")741&& !retryPostProp)) {742// do not retry the request743} else {744// try once more745openServer();746checkTunneling(httpuc);747afterConnect();748writeRequests(requests, poster);749return parseHTTP(responses, pi, httpuc);750}751}752throw e;753}754755}756757// Check whether tunnel must be open and open it if necessary758// (in the case of HTTPS with proxy)759private void checkTunneling(HttpURLConnection httpuc) throws IOException {760if (needsTunneling()) {761MessageHeader origRequests = requests;762PosterOutputStream origPoster = poster;763httpuc.doTunneling();764requests = origRequests;765poster = origPoster;766}767}768769private boolean parseHTTPHeader(MessageHeader responses, ProgressSource pi, HttpURLConnection httpuc)770throws IOException {771/* If "HTTP/*" is found in the beginning, return true. Let772* HttpURLConnection parse the mime header itself.773*774* If this isn't valid HTTP, then we don't try to parse a header775* out of the beginning of the response into the responses,776* and instead just queue up the output stream to it's very beginning.777* This seems most reasonable, and is what the NN browser does.778*/779780keepAliveConnections = -1;781keepAliveTimeout = 0;782783boolean ret = false;784byte[] b = new byte[8];785786try {787int nread = 0;788serverInput.mark(10);789while (nread < 8) {790int r = serverInput.read(b, nread, 8 - nread);791if (r < 0) {792break;793}794nread += r;795}796String keep=null;797String authenticate=null;798ret = b[0] == 'H' && b[1] == 'T'799&& b[2] == 'T' && b[3] == 'P' && b[4] == '/' &&800b[5] == '1' && b[6] == '.';801serverInput.reset();802if (ret) { // is valid HTTP - response started w/ "HTTP/1."803responses.parseHeader(serverInput);804805// we've finished parsing http headers806// check if there are any applicable cookies to set (in cache)807CookieHandler cookieHandler = httpuc.getCookieHandler();808if (cookieHandler != null) {809URI uri = ParseUtil.toURI(url);810// NOTE: That cast from Map shouldn't be necessary but811// a bug in javac is triggered under certain circumstances812// So we do put the cast in as a workaround until813// it is resolved.814if (uri != null)815cookieHandler.put(uri, responses.getHeaders());816}817818/* decide if we're keeping alive:819* This is a bit tricky. There's a spec, but most current820* servers (10/1/96) that support this differ in dialects.821* If the server/client misunderstand each other, the822* protocol should fall back onto HTTP/1.0, no keep-alive.823*/824if (usingProxy) { // not likely a proxy will return this825keep = responses.findValue("Proxy-Connection");826authenticate = responses.findValue("Proxy-Authenticate");827}828if (keep == null) {829keep = responses.findValue("Connection");830authenticate = responses.findValue("WWW-Authenticate");831}832833// 'disableKeepAlive' starts with the value false.834// It can transition from false to true, but once true835// it stays true.836// If cacheNTLMProp is false, and disableKeepAlive is false,837// then we need to examine the response headers to figure out838// whether we are doing NTLM authentication. If we do NTLM,839// and cacheNTLMProp is false, than we can't keep this connection840// alive: we will switch disableKeepAlive to true.841boolean canKeepAlive = !disableKeepAlive;842if (canKeepAlive && (cacheNTLMProp == false || cacheSPNEGOProp == false)843&& authenticate != null) {844authenticate = authenticate.toLowerCase(Locale.US);845if (cacheNTLMProp == false) {846canKeepAlive &= !authenticate.startsWith("ntlm ");847}848if (cacheSPNEGOProp == false) {849canKeepAlive &= !authenticate.startsWith("negotiate ");850canKeepAlive &= !authenticate.startsWith("kerberos ");851}852}853disableKeepAlive |= !canKeepAlive;854855if (keep != null && keep.toLowerCase(Locale.US).equals("keep-alive")) {856/* some servers, notably Apache1.1, send something like:857* "Keep-Alive: timeout=15, max=1" which we should respect.858*/859if (disableKeepAlive) {860keepAliveConnections = 1;861} else {862HeaderParser p = new HeaderParser(863responses.findValue("Keep-Alive"));864/* default should be larger in case of proxy */865keepAliveConnections = p.findInt("max", usingProxy?50:5);866keepAliveTimeout = p.findInt("timeout", usingProxy?60:5);867}868} else if (b[7] != '0') {869/*870* We're talking 1.1 or later. Keep persistent until871* the server says to close.872*/873if (keep != null || disableKeepAlive) {874/*875* The only Connection token we understand is close.876* Paranoia: if there is any Connection header then877* treat as non-persistent.878*/879keepAliveConnections = 1;880} else {881keepAliveConnections = 5;882}883}884} else if (nread != 8) {885if (!failedOnce && requests != null) {886failedOnce = true;887if (getRequestMethod().equals("CONNECT")888|| streaming889|| (httpuc.getRequestMethod().equals("POST")890&& !retryPostProp)) {891// do not retry the request892} else {893closeServer();894cachedHttpClient = false;895openServer();896checkTunneling(httpuc);897afterConnect();898writeRequests(requests, poster);899return parseHTTP(responses, pi, httpuc);900}901}902throw new SocketException("Unexpected end of file from server");903} else {904// we can't vouche for what this is....905responses.set("Content-type", "unknown/unknown");906}907} catch (IOException e) {908throw e;909}910911int code = -1;912try {913String resp;914resp = responses.getValue(0);915/* should have no leading/trailing LWS916* expedite the typical case by assuming it has917* form "HTTP/1.x <WS> 2XX <mumble>"918*/919int ind;920ind = resp.indexOf(' ');921while(resp.charAt(ind) == ' ')922ind++;923code = Integer.parseInt(resp, ind, ind + 3, 10);924} catch (Exception e) {}925926if (code == HTTP_CONTINUE && ignoreContinue) {927responses.reset();928return parseHTTPHeader(responses, pi, httpuc);929}930931long cl = -1;932933/*934* Set things up to parse the entity body of the reply.935* We should be smarter about avoid pointless work when936* the HTTP method and response code indicate there will be937* no entity body to parse.938*/939String te = responses.findValue("Transfer-Encoding");940if (te != null && te.equalsIgnoreCase("chunked")) {941serverInput = new ChunkedInputStream(serverInput, this, responses);942943/*944* If keep alive not specified then close after the stream945* has completed.946*/947if (keepAliveConnections <= 1) {948keepAliveConnections = 1;949keepingAlive = false;950} else {951keepingAlive = !disableKeepAlive;952}953failedOnce = false;954} else {955956/*957* If it's a keep alive connection then we will keep958* (alive if :-959* 1. content-length is specified, or960* 2. "Not-Modified" or "No-Content" responses - RFC 2616 states that961* 204 or 304 response must not include a message body.962*/963String cls = responses.findValue("content-length");964if (cls != null) {965try {966cl = Long.parseLong(cls);967} catch (NumberFormatException e) {968cl = -1;969}970}971String requestLine = requests.getKey(0);972973if ((requestLine != null &&974(requestLine.startsWith("HEAD"))) ||975code == HttpURLConnection.HTTP_NOT_MODIFIED ||976code == HttpURLConnection.HTTP_NO_CONTENT) {977cl = 0;978}979980if (keepAliveConnections > 1 &&981(cl >= 0 ||982code == HttpURLConnection.HTTP_NOT_MODIFIED ||983code == HttpURLConnection.HTTP_NO_CONTENT)) {984keepingAlive = !disableKeepAlive;985failedOnce = false;986} else if (keepingAlive) {987/* Previously we were keeping alive, and now we're not. Remove988* this from the cache (but only here, once) - otherwise we get989* multiple removes and the cache count gets messed up.990*/991keepingAlive=false;992}993}994995/* wrap a KeepAliveStream/MeteredStream around it if appropriate */996997if (cl > 0) {998// In this case, content length is well known, so it is okay999// to wrap the input stream with KeepAliveStream/MeteredStream.10001001if (pi != null) {1002// Progress monitor is enabled1003pi.setContentType(responses.findValue("content-type"));1004}10051006// If disableKeepAlive == true, the client will not be returned1007// to the cache. But we still need to use a keepalive stream to1008// allow the multi-message authentication exchange on the connection1009boolean useKeepAliveStream = isKeepingAlive() || disableKeepAlive;1010if (useKeepAliveStream) {1011// Wrap KeepAliveStream if keep alive is enabled.1012logFinest("KeepAlive stream used: " + url);1013serverInput = new KeepAliveStream(serverInput, pi, cl, this);1014failedOnce = false;1015}1016else {1017serverInput = new MeteredStream(serverInput, pi, cl);1018}1019}1020else if (cl == -1) {1021// In this case, content length is unknown - the input1022// stream would simply be a regular InputStream or1023// ChunkedInputStream.10241025if (pi != null) {1026// Progress monitoring is enabled.10271028pi.setContentType(responses.findValue("content-type"));10291030// Wrap MeteredStream for tracking indeterministic1031// progress, even if the input stream is ChunkedInputStream.1032serverInput = new MeteredStream(serverInput, pi, cl);1033}1034else {1035// Progress monitoring is disabled, and there is no1036// need to wrap an unknown length input stream.10371038// ** This is an no-op **1039}1040}1041else {1042if (pi != null)1043pi.finishTracking();1044}10451046return ret;1047}10481049public InputStream getInputStream() {1050lock();1051try {1052return serverInput;1053} finally {1054unlock();1055}1056}10571058public OutputStream getOutputStream() {1059return serverOutput;1060}10611062@Override1063public String toString() {1064return getClass().getName()+"("+url+")";1065}10661067public final boolean isKeepingAlive() {1068return getHttpKeepAliveSet() && keepingAlive;1069}10701071public void setCacheRequest(CacheRequest cacheRequest) {1072this.cacheRequest = cacheRequest;1073}10741075CacheRequest getCacheRequest() {1076return cacheRequest;1077}10781079String getRequestMethod() {1080if (requests != null) {1081String requestLine = requests.getKey(0);1082if (requestLine != null) {1083return requestLine.split("\\s+")[0];1084}1085}1086return "";1087}10881089public void setDoNotRetry(boolean value) {1090// failedOnce is used to determine if a request should be retried.1091failedOnce = value;1092}10931094public void setIgnoreContinue(boolean value) {1095ignoreContinue = value;1096}10971098/* Use only on connections in error. */1099@Override1100public void closeServer() {1101try {1102keepingAlive = false;1103serverSocket.close();1104} catch (Exception e) {}1105}11061107/**1108* @return the proxy host being used for this client, or null1109* if we're not going through a proxy1110*/1111public String getProxyHostUsed() {1112if (!usingProxy) {1113return null;1114} else {1115return ((InetSocketAddress)proxy.address()).getHostString();1116}1117}11181119/**1120* @return the proxy port being used for this client. Meaningless1121* if getProxyHostUsed() gives null.1122*/1123public int getProxyPortUsed() {1124if (usingProxy)1125return ((InetSocketAddress)proxy.address()).getPort();1126return -1;1127}11281129public final void lock() {1130clientLock.lock();1131}11321133public final void unlock() {1134clientLock.unlock();1135}1136}113711381139