1 /*
2 * Copyright 1994-2007 Sun Microsystems, Inc. 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. Sun designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
22 * CA 95054 USA or visit www.sun.com if you need additional information or
23 * have any questions.
24 */
25
26 package sun.net.www.http;
27
28 import java.io;
29 import java.net;
30 import java.util;
31 import sun.net.NetworkClient;
32 import sun.net.ProgressSource;
33 import sun.net.ProgressMonitor;
34 import sun.net.www.MessageHeader;
35 import sun.net.www.HeaderParser;
36 import sun.net.www.MeteredStream;
37 import sun.net.www.ParseUtil;
38 import sun.net.www.protocol.http.HttpURLConnection;
39 import sun.misc.RegexpPool;
40
41 import java.security;
42 /**
43 * @author Herb Jellinek
44 * @author Dave Brown
45 */
46 public class HttpClient extends NetworkClient {
47 // whether this httpclient comes from the cache
48 protected boolean cachedHttpClient = false;
49
50 private boolean inCache;
51
52 protected CookieHandler cookieHandler;
53
54 // Http requests we send
55 MessageHeader requests;
56
57 // Http data we send with the headers
58 PosterOutputStream poster = null;
59
60 // if we've had one io error
61 boolean failedOnce = false;
62
63 /** regexp pool of hosts for which we should connect directly, not Proxy
64 * these are intialized from a property.
65 */
66 private static RegexpPool nonProxyHostsPool = null;
67
68 /** The string source of nonProxyHostsPool
69 */
70 private static String nonProxyHostsSource = null;
71
72 /** Response code for CONTINUE */
73 private static final int HTTP_CONTINUE = 100;
74
75 /** Default port number for http daemons. REMIND: make these private */
76 static final int httpPortNumber = 80;
77
78 /** return default port number (subclasses may override) */
79 protected int getDefaultPort () { return httpPortNumber; }
80
81 static private int getDefaultPort(String proto) {
82 if ("http".equalsIgnoreCase(proto))
83 return 80;
84 if ("https".equalsIgnoreCase(proto))
85 return 443;
86 return -1;
87 }
88
89 /* The following three data members are left in for binary */
90 /* backwards-compatibility. Unfortunately, HotJava sets them directly */
91 /* when it wants to change the settings. The new design has us not */
92 /* cache these, so this is unnecessary, but eliminating the data members */
93 /* would break HJB 1.1 under JDK 1.2. */
94 /* */
95 /* These data members are not used, and their values are meaningless. */
96 /* REMIND: Take them out for JDK 2.0! */
97 /**
98 * @deprecated
99 */
100 // public static String proxyHost = null;
101 /**
102 * @deprecated
103 */
104 // public static int proxyPort = 80;
105
106 /* instance-specific proxy fields override the static fields if set.
107 * Used by FTP. These are set to the true proxy host/port if
108 * usingProxy is true.
109 */
110 // private String instProxy = null;
111 // private int instProxyPort = -1;
112
113 /* All proxying (generic as well as instance-specific) may be
114 * disabled through use of this flag
115 */
116 protected boolean proxyDisabled;
117
118 // are we using proxy in this instance?
119 public boolean usingProxy = false;
120 // target host, port for the URL
121 protected String host;
122 protected int port;
123
124 /* where we cache currently open, persistent connections */
125 protected static KeepAliveCache kac = new KeepAliveCache();
126
127 private static boolean keepAliveProp = true;
128
129 // retryPostProp is true by default so as to preserve behavior
130 // from previous releases.
131 private static boolean retryPostProp = true;
132
133 volatile boolean keepingAlive = false; /* this is a keep-alive connection */
134 int keepAliveConnections = -1; /* number of keep-alives left */
135
136 /**Idle timeout value, in milliseconds. Zero means infinity,
137 * iff keepingAlive=true.
138 * Unfortunately, we can't always believe this one. If I'm connected
139 * through a Netscape proxy to a server that sent me a keep-alive
140 * time of 15 sec, the proxy unilaterally terminates my connection
141 * after 5 sec. So we have to hard code our effective timeout to
142 * 4 sec for the case where we're using a proxy. *SIGH*
143 */
144 int keepAliveTimeout = 0;
145
146 /** whether the response is to be cached */
147 private CacheRequest cacheRequest = null;
148
149 /** Url being fetched. */
150 protected URL url;
151
152 /* if set, the client will be reused and must not be put in cache */
153 public boolean reuse = false;
154
155 /**
156 * A NOP method kept for backwards binary compatibility
157 * @deprecated -- system properties are no longer cached.
158 */
159 @Deprecated
160 public static synchronized void resetProperties() {
161 }
162
163 int getKeepAliveTimeout() {
164 return keepAliveTimeout;
165 }
166
167 static {
168 String keepAlive = java.security.AccessController.doPrivileged(
169 new sun.security.action.GetPropertyAction("http.keepAlive"));
170
171 String retryPost = java.security.AccessController.doPrivileged(
172 new sun.security.action.GetPropertyAction("sun.net.http.retryPost"));
173
174 if (keepAlive != null) {
175 keepAliveProp = Boolean.valueOf(keepAlive).booleanValue();
176 } else {
177 keepAliveProp = true;
178 }
179
180 if (retryPost != null) {
181 retryPostProp = Boolean.valueOf(retryPost).booleanValue();
182 } else
183 retryPostProp = true;
184
185 }
186
187 /**
188 * @return true iff http keep alive is set (i.e. enabled). Defaults
189 * to true if the system property http.keepAlive isn't set.
190 */
191 public boolean getHttpKeepAliveSet() {
192 return keepAliveProp;
193 }
194
195
196 protected HttpClient() {
197 }
198
199 private HttpClient(URL url)
200 throws IOException {
201 this(url, (String)null, -1, false);
202 }
203
204 protected HttpClient(URL url,
205 boolean proxyDisabled) throws IOException {
206 this(url, null, -1, proxyDisabled);
207 }
208
209 /* This package-only CTOR should only be used for FTP piggy-backed on HTTP
210 * HTTP URL's that use this won't take advantage of keep-alive.
211 * Additionally, this constructor may be used as a last resort when the
212 * first HttpClient gotten through New() failed (probably b/c of a
213 * Keep-Alive mismatch).
214 *
215 * XXX That documentation is wrong ... it's not package-private any more
216 */
217 public HttpClient(URL url, String proxyHost, int proxyPort)
218 throws IOException {
219 this(url, proxyHost, proxyPort, false);
220 }
221
222 protected HttpClient(URL url, Proxy p, int to) throws IOException {
223 proxy = (p == null) ? Proxy.NO_PROXY : p;
224 this.host = url.getHost();
225 this.url = url;
226 port = url.getPort();
227 if (port == -1) {
228 port = getDefaultPort();
229 }
230 setConnectTimeout(to);
231
232 // get the cookieHandler if there is any
233 cookieHandler = java.security.AccessController.doPrivileged(
234 new java.security.PrivilegedAction<CookieHandler>() {
235 public CookieHandler run() {
236 return CookieHandler.getDefault();
237 }
238 });
239
240 openServer();
241 }
242
243 static protected Proxy newHttpProxy(String proxyHost, int proxyPort,
244 String proto) {
245 if (proxyHost == null || proto == null)
246 return Proxy.NO_PROXY;
247 int pport = proxyPort < 0 ? getDefaultPort(proto) : proxyPort;
248 InetSocketAddress saddr = InetSocketAddress.createUnresolved(proxyHost, pport);
249 return new Proxy(Proxy.Type.HTTP, saddr);
250 }
251
252 /*
253 * This constructor gives "ultimate" flexibility, including the ability
254 * to bypass implicit proxying. Sometimes we need to be using tunneling
255 * (transport or network level) instead of proxying (application level),
256 * for example when we don't want the application level data to become
257 * visible to third parties.
258 *
259 * @param url the URL to which we're connecting
260 * @param proxy proxy to use for this URL (e.g. forwarding)
261 * @param proxyPort proxy port to use for this URL
262 * @param proxyDisabled true to disable default proxying
263 */
264 private HttpClient(URL url, String proxyHost, int proxyPort,
265 boolean proxyDisabled)
266 throws IOException {
267 this(url, proxyDisabled ? Proxy.NO_PROXY :
268 newHttpProxy(proxyHost, proxyPort, "http"), -1);
269 }
270
271 public HttpClient(URL url, String proxyHost, int proxyPort,
272 boolean proxyDisabled, int to)
273 throws IOException {
274 this(url, proxyDisabled ? Proxy.NO_PROXY :
275 newHttpProxy(proxyHost, proxyPort, "http"), to);
276 }
277
278 /* This class has no public constructor for HTTP. This method is used to
279 * get an HttpClient to the specifed URL. If there's currently an
280 * active HttpClient to that server/port, you'll get that one.
281 */
282 public static HttpClient New(URL url)
283 throws IOException {
284 return HttpClient.New(url, Proxy.NO_PROXY, -1, true);
285 }
286
287 public static HttpClient New(URL url, boolean useCache)
288 throws IOException {
289 return HttpClient.New(url, Proxy.NO_PROXY, -1, useCache);
290 }
291
292 public static HttpClient New(URL url, Proxy p, int to, boolean useCache)
293 throws IOException {
294 if (p == null) {
295 p = Proxy.NO_PROXY;
296 }
297 HttpClient ret = null;
298 /* see if one's already around */
299 if (useCache) {
300 ret = kac.get(url, null);
301 if (ret != null) {
302 if ((ret.proxy != null && ret.proxy.equals(p)) ||
303 (ret.proxy == null && p == null)) {
304 synchronized (ret) {
305 ret.cachedHttpClient = true;
306 assert ret.inCache;
307 ret.inCache = false;
308 }
309 } else {
310 // We cannot return this connection to the cache as it's
311 // KeepAliveTimeout will get reset. We simply close the connection.
312 // This should be fine as it is very rare that a connection
313 // to the same host will not use the same proxy.
314 ret.inCache = false;
315 ret.closeServer();
316 ret = null;
317 }
318 }
319 }
320 if (ret == null) {
321 ret = new HttpClient(url, p, to);
322 } else {
323 SecurityManager security = System.getSecurityManager();
324 if (security != null) {
325 security.checkConnect(url.getHost(), url.getPort());
326 }
327 ret.url = url;
328 }
329 return ret;
330 }
331
332 public static HttpClient New(URL url, Proxy p, int to) throws IOException {
333 return New(url, p, to, true);
334 }
335
336 public static HttpClient New(URL url, String proxyHost, int proxyPort,
337 boolean useCache)
338 throws IOException {
339 return New(url, newHttpProxy(proxyHost, proxyPort, "http"), -1, useCache);
340 }
341
342 public static HttpClient New(URL url, String proxyHost, int proxyPort,
343 boolean useCache, int to)
344 throws IOException {
345 return New(url, newHttpProxy(proxyHost, proxyPort, "http"), to, useCache);
346 }
347
348 /* return it to the cache as still usable, if:
349 * 1) It's keeping alive, AND
350 * 2) It still has some connections left, AND
351 * 3) It hasn't had a error (PrintStream.checkError())
352 * 4) It hasn't timed out
353 *
354 * If this client is not keepingAlive, it should have been
355 * removed from the cache in the parseHeaders() method.
356 */
357
358 public void finished() {
359 if (reuse) /* will be reused */
360 return;
361 keepAliveConnections--;
362 poster = null;
363 if (keepAliveConnections > 0 && isKeepingAlive() &&
364 !(serverOutput.checkError())) {
365 /* This connection is keepingAlive && still valid.
366 * Return it to the cache.
367 */
368 putInKeepAliveCache();
369 } else {
370 closeServer();
371 }
372 }
373
374 protected synchronized void putInKeepAliveCache() {
375 if (inCache) {
376 assert false : "Duplicate put to keep alive cache";
377 return;
378 }
379 inCache = true;
380 kac.put(url, null, this);
381 }
382
383 protected boolean isInKeepAliveCache() {
384 return inCache;
385 }
386
387 /*
388 * Close an idle connection to this URL (if it exists in the
389 * cache).
390 */
391 public void closeIdleConnection() {
392 HttpClient http = kac.get(url, null);
393 if (http != null) {
394 http.closeServer();
395 }
396 }
397
398 /* We're very particular here about what our InputStream to the server
399 * looks like for reasons that are apparent if you can decipher the
400 * method parseHTTP(). That's why this method is overidden from the
401 * superclass.
402 */
403 public void openServer(String server, int port) throws IOException {
404 serverSocket = doConnect(server, port);
405 try {
406 serverOutput = new PrintStream(
407 new BufferedOutputStream(serverSocket.getOutputStream()),
408 false, encoding);
409 } catch (UnsupportedEncodingException e) {
410 throw new InternalError(encoding+" encoding not found");
411 }
412 serverSocket.setTcpNoDelay(true);
413 }
414
415 /*
416 * Returns true if the http request should be tunneled through proxy.
417 * An example where this is the case is Https.
418 */
419 public boolean needsTunneling() {
420 return false;
421 }
422
423 /*
424 * Returns true if this httpclient is from cache
425 */
426 public boolean isCachedConnection() {
427 return cachedHttpClient;
428 }
429
430 /*
431 * Finish any work left after the socket connection is
432 * established. In the normal http case, it's a NO-OP. Subclass
433 * may need to override this. An example is Https, where for
434 * direct connection to the origin server, ssl handshake needs to
435 * be done; for proxy tunneling, the socket needs to be converted
436 * into an SSL socket before ssl handshake can take place.
437 */
438 public void afterConnect() throws IOException, UnknownHostException {
439 // NO-OP. Needs to be overwritten by HttpsClient
440 }
441
442 /*
443 * call openServer in a privileged block
444 */
445 private synchronized void privilegedOpenServer(final InetSocketAddress server)
446 throws IOException
447 {
448 try {
449 java.security.AccessController.doPrivileged(
450 new java.security.PrivilegedExceptionAction<Void>() {
451 public Void run() throws IOException {
452 openServer(server.getHostString(), server.getPort());
453 return null;
454 }
455 });
456 } catch (java.security.PrivilegedActionException pae) {
457 throw (IOException) pae.getException();
458 }
459 }
460
461 /*
462 * call super.openServer
463 */
464 private void superOpenServer(final String proxyHost,
465 final int proxyPort)
466 throws IOException, UnknownHostException
467 {
468 super.openServer(proxyHost, proxyPort);
469 }
470
471 /*
472 * call super.openServer in a privileged block
473 */
474 private synchronized void privilegedSuperOpenServer(final String proxyHost,
475 final int proxyPort)
476 throws IOException
477 {
478 try {
479 java.security.AccessController.doPrivileged(
480 new java.security.PrivilegedExceptionAction<Void>() {
481 public Void run() throws IOException {
482 superOpenServer(proxyHost, proxyPort);
483 return null;
484 }
485 });
486 } catch (java.security.PrivilegedActionException pae) {
487 throw (IOException) pae.getException();
488 }
489 }
490
491 /*
492 */
493 protected synchronized void openServer() throws IOException {
494
495 SecurityManager security = System.getSecurityManager();
496 if (security != null) {
497 security.checkConnect(host, port);
498 }
499
500 if (keepingAlive) { // already opened
501 return;
502 }
503
504 String urlHost = url.getHost().toLowerCase();
505
506 if (url.getProtocol().equals("http") ||
507 url.getProtocol().equals("https") ) {
508
509 if ((proxy != null) && (proxy.type() == Proxy.Type.HTTP)) {
510 privilegedOpenServer((InetSocketAddress) proxy.address());
511 usingProxy = true;
512 return;
513 } else {
514 // make direct connection
515 openServer(host, port);
516 usingProxy = false;
517 return;
518 }
519
520 } else {
521 /* we're opening some other kind of url, most likely an
522 * ftp url.
523 */
524 if ((proxy != null) && (proxy.type() == Proxy.Type.HTTP)) {
525 privilegedOpenServer((InetSocketAddress) proxy.address());
526 usingProxy = true;
527 return;
528 } else {
529 // make direct connection
530 super.openServer(host, port);
531 usingProxy = false;
532 return;
533 }
534 }
535 }
536
537 public String getURLFile() throws IOException {
538
539 String fileName = url.getFile();
540 if ((fileName == null) || (fileName.length() == 0))
541 fileName = "/";
542
543 /**
544 * proxyDisabled is set by subclass HttpsClient!
545 */
546 if (usingProxy && !proxyDisabled) {
547 // Do not use URLStreamHandler.toExternalForm as the fragment
548 // should not be part of the RequestURI. It should be an
549 // absolute URI which does not have a fragment part.
550 StringBuffer result = new StringBuffer(128);
551 result.append(url.getProtocol());
552 result.append(":");
553 if (url.getAuthority() != null && url.getAuthority().length() > 0) {
554 result.append("//");
555 result.append(url.getAuthority());
556 }
557 if (url.getPath() != null) {
558 result.append(url.getPath());
559 }
560 if (url.getQuery() != null) {
561 result.append('?');
562 result.append(url.getQuery());
563 }
564
565 fileName = result.toString();
566 }
567 if (fileName.indexOf('\n') == -1)
568 return fileName;
569 else
570 throw new java.net.MalformedURLException("Illegal character in URL");
571 }
572
573 /**
574 * @deprecated
575 */
576 @Deprecated
577 public void writeRequests(MessageHeader head) {
578 requests = head;
579 requests.print(serverOutput);
580 serverOutput.flush();
581 }
582
583 public void writeRequests(MessageHeader head,
584 PosterOutputStream pos) throws IOException {
585 requests = head;
586 requests.print(serverOutput);
587 poster = pos;
588 if (poster != null)
589 poster.writeTo(serverOutput);
590 serverOutput.flush();
591 }
592
593 /** Parse the first line of the HTTP request. It usually looks
594 something like: "HTTP/1.0 <number> comment\r\n". */
595
596 public boolean parseHTTP(MessageHeader responses, ProgressSource pi, HttpURLConnection httpuc)
597 throws IOException {
598 /* If "HTTP/*" is found in the beginning, return true. Let
599 * HttpURLConnection parse the mime header itself.
600 *
601 * If this isn't valid HTTP, then we don't try to parse a header
602 * out of the beginning of the response into the responses,
603 * and instead just queue up the output stream to it's very beginning.
604 * This seems most reasonable, and is what the NN browser does.
605 */
606
607 try {
608 serverInput = serverSocket.getInputStream();
609 serverInput = new BufferedInputStream(serverInput);
610 return (parseHTTPHeader(responses, pi, httpuc));
611 } catch (SocketTimeoutException stex) {
612 // We don't want to retry the request when the app. sets a timeout
613 closeServer();
614 throw stex;
615 } catch (IOException e) {
616 closeServer();
617 cachedHttpClient = false;
618 if (!failedOnce && requests != null) {
619 if (httpuc.getRequestMethod().equals("POST") && !retryPostProp) {
620 // do not retry the request
621 } else {
622 // try once more
623 failedOnce = true;
624 openServer();
625 if (needsTunneling()) {
626 httpuc.doTunneling();
627 }
628 afterConnect();
629 writeRequests(requests, poster);
630 return parseHTTP(responses, pi, httpuc);
631 }
632 }
633 throw e;
634 }
635
636 }
637
638 public int setTimeout (int timeout) throws SocketException {
639 int old = serverSocket.getSoTimeout ();
640 serverSocket.setSoTimeout (timeout);
641 return old;
642 }
643
644 private boolean parseHTTPHeader(MessageHeader responses, ProgressSource pi, HttpURLConnection httpuc)
645 throws IOException {
646 /* If "HTTP/*" is found in the beginning, return true. Let
647 * HttpURLConnection parse the mime header itself.
648 *
649 * If this isn't valid HTTP, then we don't try to parse a header
650 * out of the beginning of the response into the responses,
651 * and instead just queue up the output stream to it's very beginning.
652 * This seems most reasonable, and is what the NN browser does.
653 */
654
655 keepAliveConnections = -1;
656 keepAliveTimeout = 0;
657
658 boolean ret = false;
659 byte[] b = new byte[8];
660
661 try {
662 int nread = 0;
663 serverInput.mark(10);
664 while (nread < 8) {
665 int r = serverInput.read(b, nread, 8 - nread);
666 if (r < 0) {
667 break;
668 }
669 nread += r;
670 }
671 String keep=null;
672 ret = b[0] == 'H' && b[1] == 'T'
673 && b[2] == 'T' && b[3] == 'P' && b[4] == '/' &&
674 b[5] == '1' && b[6] == '.';
675 serverInput.reset();
676 if (ret) { // is valid HTTP - response started w/ "HTTP/1."
677 responses.parseHeader(serverInput);
678
679 // we've finished parsing http headers
680 // check if there are any applicable cookies to set (in cache)
681 if (cookieHandler != null) {
682 URI uri = ParseUtil.toURI(url);
683 // NOTE: That cast from Map shouldn't be necessary but
684 // a bug in javac is triggered under certain circumstances
685 // So we do put the cast in as a workaround until
686 // it is resolved.
687 if (uri != null)
688 cookieHandler.put(uri, responses.getHeaders());
689 }
690
691 /* decide if we're keeping alive:
692 * This is a bit tricky. There's a spec, but most current
693 * servers (10/1/96) that support this differ in dialects.
694 * If the server/client misunderstand each other, the
695 * protocol should fall back onto HTTP/1.0, no keep-alive.
696 */
697 if (usingProxy) { // not likely a proxy will return this
698 keep = responses.findValue("Proxy-Connection");
699 }
700 if (keep == null) {
701 keep = responses.findValue("Connection");
702 }
703 if (keep != null && keep.toLowerCase().equals("keep-alive")) {
704 /* some servers, notably Apache1.1, send something like:
705 * "Keep-Alive: timeout=15, max=1" which we should respect.
706 */
707 HeaderParser p = new HeaderParser(
708 responses.findValue("Keep-Alive"));
709 if (p != null) {
710 /* default should be larger in case of proxy */
711 keepAliveConnections = p.findInt("max", usingProxy?50:5);
712 keepAliveTimeout = p.findInt("timeout", usingProxy?60:5);
713 }
714 } else if (b[7] != '0') {
715 /*
716 * We're talking 1.1 or later. Keep persistent until
717 * the server says to close.
718 */
719 if (keep != null) {
720 /*
721 * The only Connection token we understand is close.
722 * Paranoia: if there is any Connection header then
723 * treat as non-persistent.
724 */
725 keepAliveConnections = 1;
726 } else {
727 keepAliveConnections = 5;
728 }
729 }
730 } else if (nread != 8) {
731 if (!failedOnce && requests != null) {
732 if (httpuc.getRequestMethod().equals("POST") && !retryPostProp) {
733 // do not retry the request
734 } else {
735 failedOnce = true;
736 closeServer();
737 cachedHttpClient = false;
738 openServer();
739 if (needsTunneling()) {
740 httpuc.doTunneling();
741 }
742 afterConnect();
743 writeRequests(requests, poster);
744 return parseHTTP(responses, pi, httpuc);
745 }
746 }
747 throw new SocketException("Unexpected end of file from server");
748 } else {
749 // we can't vouche for what this is....
750 responses.set("Content-type", "unknown/unknown");
751 }
752 } catch (IOException e) {
753 throw e;
754 }
755
756 int code = -1;
757 try {
758 String resp;
759 resp = responses.getValue(0);
760 /* should have no leading/trailing LWS
761 * expedite the typical case by assuming it has
762 * form "HTTP/1.x <WS> 2XX <mumble>"
763 */
764 int ind;
765 ind = resp.indexOf(' ');
766 while(resp.charAt(ind) == ' ')
767 ind++;
768 code = Integer.parseInt(resp.substring(ind, ind + 3));
769 } catch (Exception e) {}
770
771 if (code == HTTP_CONTINUE) {
772 responses.reset();
773 return parseHTTPHeader(responses, pi, httpuc);
774 }
775
776 long cl = -1;
777
778 /*
779 * Set things up to parse the entity body of the reply.
780 * We should be smarter about avoid pointless work when
781 * the HTTP method and response code indicate there will be
782 * no entity body to parse.
783 */
784 String te = null;
785 try {
786 te = responses.findValue("Transfer-Encoding");
787 } catch (Exception e) {}
788 if (te != null && te.equalsIgnoreCase("chunked")) {
789 serverInput = new ChunkedInputStream(serverInput, this, responses);
790
791 /*
792 * If keep alive not specified then close after the stream
793 * has completed.
794 */
795 if (keepAliveConnections <= 1) {
796 keepAliveConnections = 1;
797 keepingAlive = false;
798 } else {
799 keepingAlive = true;
800 }
801 failedOnce = false;
802 } else {
803
804 /*
805 * If it's a keep alive connection then we will keep
806 * (alive if :-
807 * 1. content-length is specified, or
808 * 2. "Not-Modified" or "No-Content" responses - RFC 2616 states that
809 * 204 or 304 response must not include a message body.
810 */
811 try {
812 cl = Long.parseLong(responses.findValue("content-length"));
813 } catch (Exception e) {}
814
815 String requestLine = requests.getKey(0);
816
817 if ((requestLine != null &&
818 (requestLine.startsWith("HEAD"))) ||
819 code == HttpURLConnection.HTTP_NOT_MODIFIED ||
820 code == HttpURLConnection.HTTP_NO_CONTENT) {
821 cl = 0;
822 }
823
824 if (keepAliveConnections > 1 &&
825 (cl >= 0 ||
826 code == HttpURLConnection.HTTP_NOT_MODIFIED ||
827 code == HttpURLConnection.HTTP_NO_CONTENT)) {
828 keepingAlive = true;
829 failedOnce = false;
830 } else if (keepingAlive) {
831 /* Previously we were keeping alive, and now we're not. Remove
832 * this from the cache (but only here, once) - otherwise we get
833 * multiple removes and the cache count gets messed up.
834 */
835 keepingAlive=false;
836 }
837 }
838
839 /* wrap a KeepAliveStream/MeteredStream around it if appropriate */
840
841 if (cl > 0) {
842 // In this case, content length is well known, so it is okay
843 // to wrap the input stream with KeepAliveStream/MeteredStream.
844
845 if (pi != null) {
846 // Progress monitor is enabled
847 pi.setContentType(responses.findValue("content-type"));
848 }
849
850 if (isKeepingAlive()) {
851 // Wrap KeepAliveStream if keep alive is enabled.
852 serverInput = new KeepAliveStream(serverInput, pi, cl, this);
853 failedOnce = false;
854 }
855 else {
856 serverInput = new MeteredStream(serverInput, pi, cl);
857 }
858 }
859 else if (cl == -1) {
860 // In this case, content length is unknown - the input
861 // stream would simply be a regular InputStream or
862 // ChunkedInputStream.
863
864 if (pi != null) {
865 // Progress monitoring is enabled.
866
867 pi.setContentType(responses.findValue("content-type"));
868
869 // Wrap MeteredStream for tracking indeterministic
870 // progress, even if the input stream is ChunkedInputStream.
871 serverInput = new MeteredStream(serverInput, pi, cl);
872 }
873 else {
874 // Progress monitoring is disabled, and there is no
875 // need to wrap an unknown length input stream.
876
877 // ** This is an no-op **
878 }
879 }
880 else {
881 if (pi != null)
882 pi.finishTracking();
883 }
884
885 return ret;
886 }
887
888 public synchronized InputStream getInputStream() {
889 return serverInput;
890 }
891
892 public OutputStream getOutputStream() {
893 return serverOutput;
894 }
895
896 public String toString() {
897 return getClass().getName()+"("+url+")";
898 }
899
900 public final boolean isKeepingAlive() {
901 return getHttpKeepAliveSet() && keepingAlive;
902 }
903
904 public void setCacheRequest(CacheRequest cacheRequest) {
905 this.cacheRequest = cacheRequest;
906 }
907
908 CacheRequest getCacheRequest() {
909 return cacheRequest;
910 }
911
912 protected void finalize() throws Throwable {
913 // This should do nothing. The stream finalizer will
914 // close the fd.
915 }
916
917 public void setDoNotRetry(boolean value) {
918 // failedOnce is used to determine if a request should be retried.
919 failedOnce = value;
920 }
921
922
923 /* Use only on connections in error. */
924 public void closeServer() {
925 try {
926 keepingAlive = false;
927 serverSocket.close();
928 } catch (Exception e) {}
929 }
930
931 /**
932 * @return the proxy host being used for this client, or null
933 * if we're not going through a proxy
934 */
935 public String getProxyHostUsed() {
936 if (!usingProxy) {
937 return null;
938 } else {
939 return ((InetSocketAddress)proxy.address()).getHostString();
940 }
941 }
942
943 /**
944 * @return the proxy port being used for this client. Meaningless
945 * if getProxyHostUsed() gives null.
946 */
947 public int getProxyPortUsed() {
948 if (usingProxy)
949 return ((InetSocketAddress)proxy.address()).getPort();
950 return -1;
951 }
952 }