001: /*
002: * @(#)HttpClient.java 1.124 06/10/10
003: *
004: * Copyright 1990-2006 Sun Microsystems, Inc. All Rights Reserved.
005: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
006: *
007: * This program is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU General Public License version
009: * 2 only, as published by the Free Software Foundation.
010: *
011: * This program is distributed in the hope that it will be useful, but
012: * WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * General Public License version 2 for more details (a copy is
015: * included at /legal/license.txt).
016: *
017: * You should have received a copy of the GNU General Public License
018: * version 2 along with this work; if not, write to the Free Software
019: * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
020: * 02110-1301 USA
021: *
022: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
023: * Clara, CA 95054 or visit www.sun.com if you need additional
024: * information or have any questions.
025: *
026: */
027:
028: package sun.net.www.http;
029:
030: import java.io.*;
031: import java.net.*;
032: import java.util.*;
033: import sun.net.NetworkClient;
034: import sun.net.ProgressEntry;
035: import sun.net.ProgressData;
036: import sun.net.www.MessageHeader;
037: import sun.net.www.HeaderParser;
038: import sun.net.www.MeteredStream;
039: import sun.misc.RegexpPool;
040:
041: import java.security.*;
042:
043: /**
044: * @version 1.115, 08/30/01
045: * @author Herb Jellinek
046: * @author Dave Brown
047: */
048: public class HttpClient extends NetworkClient {
049: // whether this httpclient comes from the cache
050: protected boolean cachedHttpClient = false;
051:
052: private boolean inCache;
053:
054: // Http requests we send
055: MessageHeader requests;
056:
057: // Http data we send with the headers
058: PosterOutputStream poster = null;
059:
060: // if we've had one io error
061: boolean failedOnce = false;
062:
063: /** regexp pool of hosts for which we should connect directly, not Proxy
064: * these are intialized from a property.
065: */
066: private static RegexpPool nonProxyHostsPool = null;
067:
068: /** The string source of nonProxyHostsPool
069: */
070: private static String nonProxyHostsSource = null;
071:
072: /** Response code for CONTINUE */
073: private static final int HTTP_CONTINUE = 100;
074:
075: /** Default port number for http daemons. TODO: make these private */
076: static final int httpPortNumber = 80;
077:
078: /** return default port number (subclasses may override) */
079: protected int getDefaultPort() {
080: return httpPortNumber;
081: }
082:
083: /* The following three data members are left in for binary */
084: /* backwards-compatibility. Unfortunately, HotJava sets them directly */
085: /* when it wants to change the settings. The new design has us not */
086: /* cache these, so this is unnecessary, but eliminating the data members */
087: /* would break HJB 1.1 under JDK 1.2. */
088: /* */
089: /* These data members are not used, and their values are meaningless. */
090: /* TODO: Take them out for JDK 2.0! */
091: /**
092: * @deprecated
093: */
094: public static String proxyHost = null;
095: /**
096: * @deprecated
097: */
098: public static int proxyPort = 80;
099:
100: /* instance-specific proxy fields override the static fields if set.
101: * Used by FTP. These are set to the true proxy host/port if
102: * usingProxy is true.
103: */
104: private String instProxy = null;
105: private int instProxyPort = -1;
106:
107: /* All proxying (generic as well as instance-specific) may be
108: * disabled through use of this flag
109: */
110: protected boolean proxyDisabled;
111:
112: // are we using proxy in this instance?
113: public boolean usingProxy = false;
114: // target host, port for the URL
115: protected String host;
116: protected int port;
117:
118: /* where we cache currently open, persistent connections */
119: protected static KeepAliveCache kac = new KeepAliveCache();
120:
121: private static boolean keepAliveProp = true;
122:
123: volatile boolean keepingAlive = false; /* this is a keep-alive connection */
124: int keepAliveConnections = -1; /* number of keep-alives left */
125:
126: /**Idle timeout value, in milliseconds. Zero means infinity,
127: * iff keepingAlive=true.
128: * Unfortunately, we can't always believe this one. If I'm connected
129: * through a Netscape proxy to a server that sent me a keep-alive
130: * time of 15 sec, the proxy unilaterally terminates my connection
131: * after 5 sec. So we have to hard code our effective timeout to
132: * 4 sec for the case where we're using a proxy. *SIGH*
133: */
134: int keepAliveTimeout = 0;
135:
136: /** Url being fetched. */
137: protected URL url;
138:
139: /* if set, the client will be reused and must not be put in cache */
140: public boolean reuse = false;
141:
142: /**
143: * A NOP method kept for backwards binary compatibility
144: * @deprecated -- system properties are no longer cached.
145: */
146: public static synchronized void resetProperties() {
147: }
148:
149: int getKeepAliveTimeout() {
150: return keepAliveTimeout;
151: }
152:
153: /**
154: * @return the proxy host to use, as defined by system properties.
155: */
156: private String getProxyHost() {
157: String host = (String) java.security.AccessController
158: .doPrivileged(new sun.security.action.GetPropertyAction(
159: "http.proxyHost"));
160: if (host == null) {
161: /* maintain compatibility with 1.0.2, before
162: * the properties namespace was so polluted
163: * and these properties were called, respectively,
164: * "proxyHost" "proxyPort". Hopefully there won't
165: * be any conflicts.
166: */
167: host = (String) java.security.AccessController
168: .doPrivileged(new sun.security.action.GetPropertyAction(
169: "proxyHost"));
170: }
171: if (host != null && host.length() == 0) {
172: /* System.getProp() will give us an empty String, ""
173: * for a defined but "empty" property.
174: */
175: host = null;
176: }
177: return host;
178: }
179:
180: /**
181: * @return the proxy port to use, as defined by system properties
182: */
183: private int getProxyPort() {
184: final int port[] = { 0 };
185: java.security.AccessController
186: .doPrivileged(new java.security.PrivilegedAction() {
187: public Object run() {
188: if (System.getProperty("http.proxyHost") != null) {
189: port[0] = Integer.getInteger(
190: "http.proxyPort", 80).intValue();
191: } else {
192: /* maintain compatibility with 1.0.2, before
193: * the properties namespace was so polluted
194: * and these properties were called, respectively,
195: * "proxyHost" "proxyPort". Hopefully there won't
196: * be any conflicts.
197: */
198: port[0] = Integer.getInteger("proxyPort",
199: 80).intValue();
200: }
201: return null;
202: }
203: });
204: return port[0];
205: }
206:
207: static {
208: String keepAlive = (String) java.security.AccessController
209: .doPrivileged(new java.security.PrivilegedAction() {
210: public Object run() {
211: return System.getProperty("http.keepAlive");
212: }
213: });
214: if (keepAlive != null) {
215: keepAliveProp = Boolean.valueOf(keepAlive).booleanValue();
216: } else {
217: keepAliveProp = true;
218: }
219: }
220:
221: /**
222: * @return true iff http keep alive is set (i.e. enabled). Defaults
223: * to true if the system property http.keepAlive isn't set.
224: */
225: public boolean getHttpKeepAliveSet() {
226: return keepAliveProp;
227: }
228:
229: /**
230: * @return true if host matches a host specified via
231: * the http.nonProxyHosts property
232: */
233: private boolean matchNonProxyHosts(String host) {
234: synchronized (getClass()) {
235: String rawList = (String) java.security.AccessController
236: .doPrivileged(new sun.security.action.GetPropertyAction(
237: "http.nonProxyHosts"));
238: if (rawList == null) {
239: nonProxyHostsPool = null;
240: } else {
241: if (!rawList.equals(nonProxyHostsSource)) {
242: RegexpPool pool = new RegexpPool();
243: StringTokenizer st = new StringTokenizer(rawList,
244: "|", false);
245: try {
246: while (st.hasMoreTokens()) {
247: pool.add(st.nextToken().toLowerCase(),
248: Boolean.TRUE);
249: }
250: } catch (sun.misc.REException ex) {
251: System.err
252: .println("Error in http.nonProxyHosts system property: "
253: + ex);
254: }
255: nonProxyHostsPool = pool;
256: }
257: }
258: nonProxyHostsSource = rawList;
259: }
260:
261: /*
262: * Match against non-proxy hosts
263: */
264: if (nonProxyHostsPool == null) {
265: return false;
266: }
267: if (nonProxyHostsPool.match(host) != null) {
268: return true;
269: } else {
270: return false;
271: }
272: }
273:
274: protected HttpClient() {
275: }
276:
277: private HttpClient(URL url) throws IOException {
278: this (url, (String) null, -1, false);
279: }
280:
281: protected HttpClient(URL url, boolean proxyDisabled)
282: throws IOException {
283: this (url, null, -1, proxyDisabled);
284: }
285:
286: /* This package-only CTOR should only be used for FTP piggy-backed on HTTP
287: * HTTP URL's that use this won't take advantage of keep-alive.
288: * Additionally, this constructor may be used as a last resort when the
289: * first HttpClient gotten through New() failed (probably b/c of a
290: * Keep-Alive mismatch).
291: *
292: * NOTE: That documentation is wrong ... it's not package-private any more
293: */
294: public HttpClient(URL url, String proxy, int proxyPort)
295: throws IOException {
296: this (url, proxy, proxyPort, false);
297: }
298:
299: /*
300: * This constructor gives "ultimate" flexibility, including the ability
301: * to bypass implicit proxying. Sometimes we need to be using tunneling
302: * (transport or network level) instead of proxying (application level),
303: * for example when we don't want the application level data to become
304: * visible to third parties.
305: *
306: * @param url the URL to which we're connecting
307: * @param proxy proxy to use for this URL (e.g. forwarding)
308: * @param proxyPort proxy port to use for this URL
309: * @param proxyDisabled true to disable default proxying
310: */
311: private HttpClient(URL url, String proxy, int proxyPort,
312: boolean proxyDisabled) throws IOException {
313:
314: this .proxyDisabled = proxyDisabled;
315: if (!proxyDisabled) {
316: this .instProxy = proxy;
317: this .instProxyPort = (proxyPort < 0) ? getDefaultPort()
318: : proxyPort;
319: }
320: /* try to set host to "%d.%d.%d.%d" string if
321: * visible - Sprint bug - brown */
322: try {
323: InetAddress addr = InetAddress.getByName(url.getHost());
324: this .host = addr.getHostAddress();
325: } catch (UnknownHostException ignored) {
326: this .host = url.getHost();
327: }
328: this .url = url;
329: port = url.getPort();
330: if (port == -1) {
331: port = getDefaultPort();
332: }
333: openServer();
334: }
335:
336: /* This class has no public constructor for HTTP. This method is used to
337: * get an HttpClient to the specifed URL. If there's currently an
338: * active HttpClient to that server/port, you'll get that one.
339: */
340: public static HttpClient New(URL url) throws IOException {
341: return HttpClient.New(url, true);
342: }
343:
344: public static HttpClient New(URL url, boolean useCache)
345: throws IOException {
346: return HttpClient.New(url, (String) null, -1, useCache);
347: }
348:
349: public static HttpClient New(URL url, String proxy, int proxyPort,
350: boolean useCache) throws IOException {
351: HttpClient ret = null;
352: if (useCache) {
353: /* see if one's already around */
354: ret = (HttpClient) kac.get(url, null);
355: if (ret != null) {
356: synchronized (ret) {
357: ret.cachedHttpClient = true;
358: // Uncomment this line if assertions are always
359: // turned on for libraries
360: //assert ret.inCache;
361: ret.inCache = false;
362: }
363: }
364: }
365: if (ret == null) {
366: ret = new HttpClient(url, proxy, proxyPort);
367: } else {
368: SecurityManager security = System.getSecurityManager();
369: if (security != null) {
370: security.checkConnect(url.getHost(), url.getPort());
371: }
372: ret.url = url;
373: }
374:
375: return ret;
376: }
377:
378: /* return it to the cache as still usable, if:
379: * 1) It's keeping alive, AND
380: * 2) It still has some connections left, AND
381: * 3) It hasn't had a error (PrintStream.checkError())
382: * 4) It hasn't timed out
383: *
384: * If this client is not keepingAlive, it should have been
385: * removed from the cache in the parseHeaders() method.
386: */
387:
388: public void finished() {
389: if (reuse) /* will be reused */
390: return;
391: keepAliveConnections--;
392: if (keepAliveConnections > 0 && isKeepingAlive()
393: && !(serverOutput.checkError())) {
394: /* This connection is keepingAlive && still valid.
395: * Return it to the cache.
396: */
397: putInKeepAliveCache();
398: } else {
399: closeServer();
400: }
401: }
402:
403: protected synchronized void putInKeepAliveCache() {
404: if (inCache) {
405: // Uncomment this line if assertions are always
406: // turned on for libraries
407: //assert false : "Duplicate put to keep alive cache";
408: return;
409: }
410: inCache = true;
411: kac.put(url, null, this );
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 = (HttpClient) 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: public void openServer(String server, int port) throws IOException {
431: serverSocket = doConnect(server, port);
432: try {
433: serverOutput = new PrintStream(new BufferedOutputStream(
434: serverSocket.getOutputStream()), false, encoding);
435: } catch (UnsupportedEncodingException e) {
436: throw new InternalError(encoding + " encoding not found");
437: }
438: serverSocket.setTcpNoDelay(true);
439: }
440:
441: /*
442: * Returns true if the http request should be tunneled through proxy.
443: * An example where this is the case is Https.
444: */
445: public boolean needsTunneling() {
446: return false;
447: }
448:
449: /*
450: * Returns true if this httpclient is from cache
451: */
452: public boolean isCachedConnection() {
453: return cachedHttpClient;
454: }
455:
456: /*
457: * Finish any work left after the socket connection is
458: * established. In the normal http case, it's a NO-OP. Subclass
459: * may need to override this. An example is Https, where for
460: * direct connection to the origin server, ssl handshake needs to
461: * be done; for proxy tunneling, the socket needs to be converted
462: * into an SSL socket before ssl handshake can take place.
463: */
464: public void afterConnect() throws IOException, UnknownHostException {
465: // NO-OP. Needs to be overwritten by HttpsClient
466: }
467:
468: /*
469: * call openServer in a privileged block
470: */
471: private synchronized void privilegedOpenServer(
472: final String proxyHost, final int proxyPort)
473: throws IOException {
474: try {
475: java.security.AccessController
476: .doPrivileged(new java.security.PrivilegedExceptionAction() {
477: public Object run() throws IOException {
478: openServer(proxyHost, proxyPort);
479: return null;
480: }
481: });
482: } catch (java.security.PrivilegedActionException pae) {
483: throw (IOException) pae.getException();
484: }
485: }
486:
487: /*
488: * call super.openServer
489: */
490: private void super OpenServer(final String proxyHost,
491: final int proxyPort) throws IOException,
492: UnknownHostException {
493: super .openServer(proxyHost, proxyPort);
494: }
495:
496: /*
497: * call super.openServer in a privileged block
498: */
499: private synchronized void privilegedSuperOpenServer(
500: final String proxyHost, final int proxyPort)
501: throws IOException {
502: try {
503: java.security.AccessController
504: .doPrivileged(new java.security.PrivilegedExceptionAction() {
505: public Object run() throws IOException {
506: super OpenServer(proxyHost, proxyPort);
507: return null;
508: }
509: });
510: } catch (java.security.PrivilegedActionException pae) {
511: throw (IOException) pae.getException();
512: }
513: }
514:
515: /**
516: * Determines if the given host address is a loopback address or
517: * not. Any IP address starting with 127 is a loopback address
518: * although typically this is set to 127.0.0.1. A request for
519: * the address "localhost" will also be treated as loopback.
520: */
521: private boolean isLoopback(String host) {
522:
523: if (host == null || host.length() == 0)
524: return false;
525:
526: if (host.equalsIgnoreCase("localhost"))
527: return true;
528:
529: if (!Character.isDigit(host.charAt(0))) {
530: return false;
531: } else {
532: /* The string (probably) represents a numerical IP address.
533: * Parse it into int's. Compare the first int
534: * component to 127 and return if it is not
535: * 127. Otherwise, just make sure the rest of the
536: * address is a valid address.
537: */
538:
539: boolean firstInt = true;
540:
541: int hitDots = 0;
542: char[] data = host.toCharArray();
543:
544: for (int i = 0; i < data.length; i++) {
545: char c = data[i];
546: if (c < 48 || c > 57) { // !digit
547: return false;
548: }
549: int b = 0x00; // the value of one integer component
550: while (c != '.') {
551: if (c < 48 || c > 57) { // !digit
552: return false;
553: }
554: b = b * 10 + c - '0';
555:
556: if (++i >= data.length)
557: break;
558: c = data[i];
559: }
560: if (b > 0xFF) { /* invalid - bigger than a byte */
561: return false;
562: }
563:
564: /*
565: * If the first integer component is not 127 we may
566: * as well stop.
567: */
568: if (firstInt) {
569: firstInt = false;
570: if (b != 0x7F)
571: return false;
572: }
573:
574: hitDots++;
575: }
576:
577: if (hitDots != 4 || host.endsWith(".")) {
578: return false;
579: }
580: }
581:
582: return true;
583: }
584:
585: /*
586: */
587: protected synchronized void openServer() throws IOException {
588:
589: SecurityManager security = System.getSecurityManager();
590: if (security != null) {
591: security.checkConnect(host, port);
592: }
593:
594: if (keepingAlive) { // already opened
595: return;
596: }
597:
598: /* Try to open connections in this order, return on the
599: * first one that's successful:
600: * 1) if (instProxy != null)
601: * connect to instProxy
602: * _raise_exception_ if failure
603: * usingProxy=true
604: * 2) if (proxyHost != null) and !(proxyDisabled || host is on dontProxy list)
605: * connect to proxyHost;
606: * usingProxy=true;
607: * 3) connect locally
608: * usingProxy = false;
609: */
610:
611: String urlHost = url.getHost().toLowerCase();
612: boolean loopback = isLoopback(urlHost);
613:
614: if (url.getProtocol().equals("http")
615: || url.getProtocol().equals("https")) {
616:
617: if ((instProxy != null) && !loopback) {
618: privilegedOpenServer(instProxy, instProxyPort);
619: usingProxy = true;
620: return;
621: }
622:
623: String proxyHost = getProxyHost();
624: if ((proxyHost != null)
625: && (!(proxyDisabled || loopback
626: || matchNonProxyHosts(urlHost) || matchNonProxyHosts(host)))) {
627: try {
628: int proxyPort = getProxyPort();
629: privilegedOpenServer(proxyHost, proxyPort);
630: instProxy = proxyHost;
631: instProxyPort = proxyPort;
632: usingProxy = true;
633: return;
634: } catch (IOException e) {
635: // continue in this case - try locally
636: }
637: }
638: // try locally - let Exception get raised
639: openServer(host, port);
640: usingProxy = false;
641: return;
642:
643: } else {
644: /* we're opening some other kind of url, most likely an
645: * ftp url. In this case:
646: * 1) try instProxy (== FTP proxy)
647: * 2) try HttpProxy
648: * 3) directly
649: */
650: if ((instProxy != null) && !loopback) {
651: privilegedSuperOpenServer(instProxy, instProxyPort);
652: usingProxy = true;
653: return;
654: }
655:
656: String proxyHost = getProxyHost();
657: if ((proxyHost != null)
658: && (!(proxyDisabled || loopback
659: || matchNonProxyHosts(urlHost) || matchNonProxyHosts(host)))) {
660: try {
661: int proxyPort = getProxyPort();
662: privilegedSuperOpenServer(proxyHost, proxyPort);
663: instProxy = proxyHost;
664: instProxyPort = proxyPort;
665: usingProxy = true;
666: return;
667: } catch (IOException e) {
668: // continue in this case - try locally
669: }
670: }
671: // try locally
672: super .openServer(host, port);
673: usingProxy = false;
674: return;
675: }
676: }
677:
678: public String getURLFile() throws IOException {
679:
680: String fileName = url.getFile();
681: if ((fileName == null) || (fileName.length() == 0))
682: fileName = "/";
683:
684: if (usingProxy) {
685: fileName = url.toExternalForm();
686: }
687: if (fileName.indexOf('\n') == -1)
688: return fileName;
689: else
690: throw new java.net.MalformedURLException(
691: "Illegal character in URL");
692: }
693:
694: /**
695: * @deprecated
696: */
697:
698: public void writeRequests(MessageHeader head) {
699: requests = head;
700: requests.print(serverOutput);
701: serverOutput.flush();
702: }
703:
704: public void writeRequests(MessageHeader head, PosterOutputStream pos)
705: throws IOException {
706: requests = head;
707: requests.print(serverOutput);
708: poster = pos;
709: if (poster != null)
710: poster.writeTo(serverOutput);
711: serverOutput.flush();
712: }
713:
714: /** Parse the first line of the HTTP request. It usually looks
715: something like: "HTTP/1.0 <number> comment\r\n". */
716:
717: public boolean parseHTTP(MessageHeader responses, ProgressEntry pe)
718: throws IOException {
719: /* If "HTTP/*" is found in the beginning, return true. Let
720: * HttpURLConnection parse the mime header itself.
721: *
722: * If this isn't valid HTTP, then we don't try to parse a header
723: * out of the beginning of the response into the responses,
724: * and instead just queue up the output stream to it's very beginning.
725: * This seems most reasonable, and is what the NN browser does.
726: */
727:
728: try {
729: serverInput = serverSocket.getInputStream();
730: serverInput = new BufferedInputStream(serverInput);
731: return (parseHTTPHeader(responses, pe));
732: } catch (IOException e) {
733: closeServer();
734: if (!failedOnce && requests != null) {
735: // try once more
736: failedOnce = true;
737: openServer();
738: writeRequests(requests, poster);
739: return parseHTTP(responses, pe);
740: } else {
741: throw e;
742: }
743: }
744:
745: }
746:
747: public int setTimeout(int timeout) throws SocketException {
748: int old = serverSocket.getSoTimeout();
749: serverSocket.setSoTimeout(timeout);
750: return old;
751: }
752:
753: private boolean parseHTTPHeader(MessageHeader responses,
754: ProgressEntry pe) throws IOException {
755: /* If "HTTP/*" is found in the beginning, return true. Let
756: * HttpURLConnection parse the mime header itself.
757: *
758: * If this isn't valid HTTP, then we don't try to parse a header
759: * out of the beginning of the response into the responses,
760: * and instead just queue up the output stream to it's very beginning.
761: * This seems most reasonable, and is what the NN browser does.
762: */
763:
764: keepAliveConnections = -1;
765: keepAliveTimeout = 0;
766:
767: boolean ret = false;
768: byte[] b = new byte[8];
769:
770: try {
771: int nread = 0;
772: serverInput.mark(10);
773: while (nread < 8) {
774: int r = serverInput.read(b, nread, 8 - nread);
775: if (r < 0) {
776: break;
777: }
778: nread += r;
779: }
780: String keep = null;
781: ret = b[0] == 'H' && b[1] == 'T' && b[2] == 'T'
782: && b[3] == 'P' && b[4] == '/' && b[5] == '1'
783: && b[6] == '.';
784: serverInput.reset();
785: if (ret) { // is valid HTTP - response started w/ "HTTP/1."
786: responses.parseHeader(serverInput);
787: /* decide if we're keeping alive:
788: * Note: There's a spec, but most current
789: * servers (10/1/96) that support this differ in dialects.
790: * If the server/client misunderstand each other, the
791: * protocol should fall back onto HTTP/1.0, no keep-alive.
792: */
793: if (usingProxy) { // not likely a proxy will return this
794: keep = responses.findValue("Proxy-Connection");
795: }
796: if (keep == null) {
797: keep = responses.findValue("Connection");
798: }
799: if (keep != null
800: && keep.toLowerCase().equals("keep-alive")) {
801: /* some servers, notably Apache1.1, send something like:
802: * "Keep-Alive: timeout=15, max=1" which we should respect.
803: */
804: HeaderParser p = new HeaderParser(responses
805: .findValue("Keep-Alive"));
806: if (p != null) {
807: /* default should be larger in case of proxy */
808: keepAliveConnections = p.findInt("max",
809: usingProxy ? 50 : 5);
810: keepAliveTimeout = p.findInt("timeout",
811: usingProxy ? 60 : 5);
812: }
813: } else if (b[7] != '0') {
814: /*
815: * We're talking 1.1 or later. Keep persistent until
816: * the server says to close.
817: */
818: if (keep != null) {
819: /*
820: * The only Connection token we understand is close.
821: * Paranoia: if there is any Connection header then
822: * treat as non-persistent.
823: */
824: keepAliveConnections = 1;
825: } else {
826: keepAliveConnections = 5;
827: }
828: }
829: } else if (nread != 8) {
830: if (!failedOnce && requests != null) {
831: failedOnce = true;
832: closeServer();
833: openServer();
834: writeRequests(requests, poster);
835: return parseHTTP(responses, pe);
836: }
837: throw new SocketException(
838: "Unexpected end of file from server");
839: } else {
840: // we can't vouche for what this is....
841: responses.set("Content-type", "unknown/unknown");
842: }
843: } catch (IOException e) {
844: throw e;
845: }
846:
847: int code = -1;
848: try {
849: String resp;
850: resp = responses.getValue(0);
851: /* should have no leading/trailing LWS
852: * expedite the typical case by assuming it has
853: * form "HTTP/1.x <WS> 2XX <mumble>"
854: */
855: int ind;
856: ind = resp.indexOf(' ');
857: while (resp.charAt(ind) == ' ')
858: ind++;
859: code = Integer.parseInt(resp.substring(ind, ind + 3));
860: } catch (Exception e) {
861: }
862:
863: if (code == HTTP_CONTINUE) {
864: responses.reset();
865: return parseHTTPHeader(responses, pe);
866: }
867:
868: int cl = -1;
869:
870: /*
871: * Set things up to parse the entity body of the reply.
872: * We should be smarter about avoid pointless work when
873: * the HTTP method and response code indicate there will be
874: * no entity body to parse.
875: */
876: String te = null;
877: try {
878: te = responses.findValue("Transfer-Encoding");
879: } catch (Exception e) {
880: }
881: if (te != null && te.equalsIgnoreCase("chunked")) {
882: serverInput = new ChunkedInputStream(serverInput, this ,
883: responses);
884:
885: /*
886: * If keep alive not specified then close after the stream
887: * has completed.
888: */
889: if (keepAliveConnections < 0) {
890: keepAliveConnections = 1;
891: }
892: keepingAlive = true;
893: failedOnce = false;
894: } else {
895:
896: /*
897: * If it's a keep alive connection then we will keep
898: * (alive if :-
899: * 1. content-length is specified, or
900: * 2. "Not-Modified" or "No-Content" responses - RFC 2616 states that
901: * 204 or 304 response must not include a message body.
902: */
903: try {
904: cl = Integer.parseInt(responses
905: .findValue("content-length"));
906: } catch (Exception e) {
907: }
908:
909: if (keepAliveConnections > 1
910: && (cl >= 0
911: || code == HttpURLConnection.HTTP_NOT_MODIFIED || code == HttpURLConnection.HTTP_NO_CONTENT)) {
912: keepingAlive = true;
913: } else if (keepingAlive) {
914: /* Previously we were keeping alive, and now we're not. Remove
915: * this from the cache (but only here, once) - otherwise we get
916: * multiple removes and the cache count gets messed up.
917: */
918: keepingAlive = false;
919: }
920: }
921:
922: /* finally wrap a KeepAlive/MeteredStream around it if appropriate */
923: if (cl > 0) {
924: pe.setType(url.getFile(), responses
925: .findValue("content-type"));
926: pe.update(0, cl);
927: if (isKeepingAlive()) {
928: serverInput = new KeepAliveStream(serverInput, pe, this );
929: failedOnce = false;
930: } else {
931: serverInput = new MeteredStream(serverInput, pe);
932: }
933: } else {
934: ProgressData.pdata.unregister(pe);
935: }
936: return ret;
937: }
938:
939: public synchronized InputStream getInputStream() {
940: return serverInput;
941: }
942:
943: public OutputStream getOutputStream() {
944: return serverOutput;
945: }
946:
947: public String toString() {
948: return getClass().getName() + "(" + url + ")";
949: }
950:
951: public final boolean isKeepingAlive() {
952: return getHttpKeepAliveSet() && keepingAlive;
953: }
954:
955: protected void finalize() throws Throwable {
956: // This should do nothing. The stream finalizer will
957: // close the fd.
958: }
959:
960: /* Use only on connections in error. */
961: public void closeServer() {
962: try {
963: keepingAlive = false;
964: serverSocket.close();
965: } catch (Exception e) {
966: }
967: }
968:
969: /**
970: * @return the proxy host being used for this client, or null
971: * if we're not going through a proxy
972: */
973: public String getProxyHostUsed() {
974: if (!usingProxy) {
975: return null;
976: } else {
977: return instProxy;
978: }
979: }
980:
981: /**
982: * @return the proxy port being used for this client. Meaningless
983: * if getProxyHostUsed() gives null.
984: */
985: public int getProxyPortUsed() {
986: return instProxyPort;
987: }
988: }
|