Home | History | Annotate | Download | only in http
      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