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