• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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) */
getDefaultPort()71     protected int getDefaultPort () { return httpPortNumber; }
72 
getDefaultPort(String proto)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();
logFinest(String msg)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
resetProperties()138     public static synchronized void resetProperties() {
139     }
140 
getKeepAliveTimeout()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      */
getHttpKeepAliveSet()169     public boolean getHttpKeepAliveSet() {
170         return keepAliveProp;
171     }
172 
173 
HttpClient()174     protected HttpClient() {
175     }
176 
HttpClient(URL url)177     private HttpClient(URL url)
178     throws IOException {
179         this(url, (String)null, -1, false);
180     }
181 
HttpClient(URL url, boolean proxyDisabled)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      */
HttpClient(URL url, String proxyHost, int proxyPort)195     public HttpClient(URL url, String proxyHost, int proxyPort)
196     throws IOException {
197         this(url, proxyHost, proxyPort, false);
198     }
199 
HttpClient(URL url, Proxy p, int to)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 
newHttpProxy(String proxyHost, int proxyPort, String proto)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      */
privilegedOpenServer(final InetSocketAddress server)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      */
superOpenServer(final String proxyHost, final int proxyPort)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      */
openServer()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 
getURLFile()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
writeRequests(MessageHeader head)590     public void writeRequests(MessageHeader head) {
591         requests = head;
592         requests.print(serverOutput);
593         serverOutput.flush();
594     }
595 
writeRequests(MessageHeader head, PosterOutputStream pos)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 
writeRequests(MessageHeader head, PosterOutputStream pos, boolean streaming)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 
parseHTTP(MessageHeader responses, ProgressSource pi, HttpURLConnection httpuc)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 
parseHTTPHeader(MessageHeader responses, ProgressSource pi, HttpURLConnection httpuc)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 
getInputStream()915     public synchronized InputStream getInputStream() {
916         return serverInput;
917     }
918 
getOutputStream()919     public OutputStream getOutputStream() {
920         return serverOutput;
921     }
922 
923     @Override
toString()924     public String toString() {
925         return getClass().getName()+"("+url+")";
926     }
927 
isKeepingAlive()928     public final boolean isKeepingAlive() {
929         return getHttpKeepAliveSet() && keepingAlive;
930     }
931 
setCacheRequest(CacheRequest cacheRequest)932     public void setCacheRequest(CacheRequest cacheRequest) {
933         this.cacheRequest = cacheRequest;
934     }
935 
getCacheRequest()936     CacheRequest getCacheRequest() {
937         return cacheRequest;
938     }
939 
getRequestMethod()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
finalize()951     protected void finalize() throws Throwable {
952         // This should do nothing.  The stream finalizer will
953         // close the fd.
954     }
955 
setDoNotRetry(boolean value)956     public void setDoNotRetry(boolean value) {
957         // failedOnce is used to determine if a request should be retried.
958         failedOnce = value;
959     }
960 
setIgnoreContinue(boolean value)961     public void setIgnoreContinue(boolean value) {
962         ignoreContinue = value;
963     }
964 
965     /* Use only on connections in error. */
966     @Override
closeServer()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      */
getProxyHostUsed()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      */
getProxyPortUsed()990     public int getProxyPortUsed() {
991         if (usingProxy)
992             return ((InetSocketAddress)proxy.address()).getPort();
993         return -1;
994     }
995 }
996