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 specifed URL. If there's currently an
251 * active HttpClient to that server/port, you'll get that one.
252 */
253 public static HttpClient New(URL url)
254 throws IOException {
255 return HttpClient.New(url, Proxy.NO_PROXY, -1, true, null);
256 }
257
258 public static HttpClient New(URL url, boolean useCache)
259 throws IOException {
260 return HttpClient.New(url, Proxy.NO_PROXY, -1, useCache, null);
261 }
262
263 public static HttpClient New(URL url, Proxy p, int to, boolean useCache,
264 HttpURLConnection httpuc) throws IOException
265 {
266 if (p == null) {
267 p = Proxy.NO_PROXY;
268 }
269 HttpClient ret = null;
270 /* see if one's already around */
271 if (useCache) {
272 ret = kac.get(url, null);
273 if (ret != null && httpuc != null &&
274 httpuc.streaming() &&
275 httpuc.getRequestMethod() == "POST") {
276 if (!ret.available()) {
277 ret.inCache = false;
278 ret.closeServer();
279 ret = null;
280 }
281 }
282
283 if (ret != null) {
284 if ((ret.proxy != null && ret.proxy.equals(p)) ||
285 (ret.proxy == null && p == null)) {
286 synchronized (ret) {
287 ret.cachedHttpClient = true;
288 assert ret.inCache;
289 ret.inCache = false;
290 if (httpuc != null && ret.needsTunneling())
291 httpuc.setTunnelState(TUNNELING);
292 logFinest("KeepAlive stream retrieved from the cache, " + ret);
293 }
294 } else {
295 // We cannot return this connection to the cache as it's
296 // KeepAliveTimeout will get reset. We simply close the connection.
297 // This should be fine as it is very rare that a connection
298 // to the same host will not use the same proxy.
299 synchronized(ret) {
300 ret.inCache = false;
301 ret.closeServer();
302 }
303 ret = null;
304 }
305 }
306 }
307 if (ret == null) {
308 ret = new HttpClient(url, p, to);
309 } else {
310 SecurityManager security = System.getSecurityManager();
311 if (security != null) {
312 if (ret.proxy == Proxy.NO_PROXY || ret.proxy == null) {
313 security.checkConnect(InetAddress.getByName(url.getHost()).getHostAddress(), url.getPort());
314 } else {
315 security.checkConnect(url.getHost(), url.getPort());
316 }
317 }
318 ret.url = url;
319 }
320 return ret;
321 }
322
323 public static HttpClient New(URL url, Proxy p, int to,
324 HttpURLConnection httpuc) throws IOException
325 {
326 return New(url, p, to, true, httpuc);
327 }
328
329 public static HttpClient New(URL url, String proxyHost, int proxyPort,
330 boolean useCache)
331 throws IOException {
332 return New(url, newHttpProxy(proxyHost, proxyPort, "http"),
333 -1, useCache, null);
334 }
335
336 public static HttpClient New(URL url, String proxyHost, int proxyPort,
337 boolean useCache, int to,
338 HttpURLConnection httpuc)
339 throws IOException {
340 return New(url, newHttpProxy(proxyHost, proxyPort, "http"),
341 to, useCache, httpuc);
342 }
343
344 /* return it to the cache as still usable, if:
345 * 1) It's keeping alive, AND
346 * 2) It still has some connections left, AND
347 * 3) It hasn't had a error (PrintStream.checkError())
348 * 4) It hasn't timed out
349 *
350 * If this client is not keepingAlive, it should have been
351 * removed from the cache in the parseHeaders() method.
352 */
353
354 public void finished() {
355 if (reuse) /* will be reused */
356 return;
357 keepAliveConnections--;
358 poster = null;
359 if (keepAliveConnections > 0 && isKeepingAlive() &&
360 !(serverOutput.checkError())) {
361 /* This connection is keepingAlive && still valid.
362 * Return it to the cache.
363 */
364 putInKeepAliveCache();
365 } else {
366 closeServer();
367 }
368 }
369
370 protected synchronized boolean available() {
371 boolean available = true;
372 int old = -1;
373
374 try {
375 try {
376 old = serverSocket.getSoTimeout();
377 serverSocket.setSoTimeout(1);
378 BufferedInputStream tmpbuf =
379 new BufferedInputStream(serverSocket.getInputStream());
380 int r = tmpbuf.read();
381 if (r == -1) {
382 logFinest("HttpClient.available(): " +
383 "read returned -1: not available");
384 available = false;
385 }
386 } catch (SocketTimeoutException e) {
387 logFinest("HttpClient.available(): " +
388 "SocketTimeout: its available");
389 } finally {
390 if (old != -1)
391 serverSocket.setSoTimeout(old);
392 }
393 } catch (IOException e) {
394 logFinest("HttpClient.available(): " +
395 "SocketException: not available");
396 available = false;
397 }
398 return available;
399 }
400
401 protected synchronized void putInKeepAliveCache() {
402 if (inCache) {
403 assert false : "Duplicate put to keep alive cache";
404 return;
405 }
406 inCache = true;
407 kac.put(url, null, this);
408 }
409
410 protected synchronized boolean isInKeepAliveCache() {
411 return inCache;
412 }
413
414 /*
415 * Close an idle connection to this URL (if it exists in the
416 * cache).
417 */
418 public void closeIdleConnection() {
419 HttpClient http = kac.get(url, null);
420 if (http != null) {
421 http.closeServer();
422 }
423 }
424
425 /* We're very particular here about what our InputStream to the server
426 * looks like for reasons that are apparent if you can decipher the
427 * method parseHTTP(). That's why this method is overidden from the
428 * superclass.
429 */
430 @Override
431 public void openServer(String server, int port) throws IOException {
432 serverSocket = doConnect(server, port);
433 try {
434 OutputStream out = serverSocket.getOutputStream();
435 if (capture != null) {
436 out = new HttpCaptureOutputStream(out, capture);
437 }
438 serverOutput = new PrintStream(
439 new BufferedOutputStream(out),
440 false, encoding);
441 } catch (UnsupportedEncodingException e) {
442 throw new InternalError(encoding+" encoding not found", 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 = url.getFile();
553 if ((fileName == null) || (fileName.length() == 0))
554 fileName = "/";
555
556 /**
557 * proxyDisabled is set by subclass HttpsClient!
558 */
559 if (usingProxy && !proxyDisabled) {
560 // Do not use URLStreamHandler.toExternalForm as the fragment
561 // should not be part of the RequestURI. It should be an
562 // absolute URI which does not have a fragment part.
563 StringBuffer result = new StringBuffer(128);
564 result.append(url.getProtocol());
565 result.append(":");
566 if (url.getAuthority() != null && url.getAuthority().length() > 0) {
567 result.append("//");
568 result.append(url.getAuthority());
569 }
570 if (url.getPath() != null) {
571 result.append(url.getPath());
572 }
573 if (url.getQuery() != null) {
574 result.append('?');
575 result.append(url.getQuery());
576 }
577
578 fileName = result.toString();
579 }
580 if (fileName.indexOf('\n') == -1)
581 return fileName;
582 else
583 throw new java.net.MalformedURLException("Illegal character in URL");
584 }
585
586 /**
587 * @deprecated
588 */
589 @Deprecated
590 public void writeRequests(MessageHeader head) {
591 requests = head;
592 requests.print(serverOutput);
593 serverOutput.flush();
594 }
595
596 public void writeRequests(MessageHeader head,
597 PosterOutputStream pos) throws IOException {
598 requests = head;
599 requests.print(serverOutput);
600 poster = pos;
601 if (poster != null)
602 poster.writeTo(serverOutput);
603 serverOutput.flush();
604 }
605
606 public void writeRequests(MessageHeader head,
607 PosterOutputStream pos,
608 boolean streaming) throws IOException {
609 this.streaming = streaming;
610 writeRequests(head, pos);
611 }
612
613 /** Parse the first line of the HTTP request. It usually looks
614 something like: "HTTP/1.0 <number> comment\r\n". */
615
616 public boolean parseHTTP(MessageHeader responses, ProgressSource pi, HttpURLConnection httpuc)
617 throws IOException {
618 /* If "HTTP/*" is found in the beginning, return true. Let
619 * HttpURLConnection parse the mime header itself.
620 *
621 * If this isn't valid HTTP, then we don't try to parse a header
622 * out of the beginning of the response into the responses,
623 * and instead just queue up the output stream to it's very beginning.
624 * This seems most reasonable, and is what the NN browser does.
625 */
626
627 try {
628 serverInput = serverSocket.getInputStream();
629 if (capture != null) {
630 serverInput = new HttpCaptureInputStream(serverInput, capture);
631 }
632 serverInput = new BufferedInputStream(serverInput);
633 return (parseHTTPHeader(responses, pi, httpuc));
634 } catch (SocketTimeoutException stex) {
635 // We don't want to retry the request when the app. sets a timeout
636 // but don't close the server if timeout while waiting for 100-continue
637 if (ignoreContinue) {
638 closeServer();
639 }
640 throw stex;
641 } catch (IOException e) {
642 closeServer();
643 cachedHttpClient = false;
644 if (!failedOnce && requests != null) {
645 failedOnce = true;
646 if (getRequestMethod().equals("CONNECT") ||
647 (httpuc.getRequestMethod().equals("POST") &&
648 (!retryPostProp || streaming))) {
649 // do not retry the request
650 } else {
651 // try once more
652 openServer();
653 if (needsTunneling()) {
654 httpuc.doTunneling();
655 }
656 afterConnect();
657 writeRequests(requests, poster);
658 return parseHTTP(responses, pi, httpuc);
659 }
660 }
661 throw e;
662 }
663
664 }
665
666 private boolean parseHTTPHeader(MessageHeader responses, ProgressSource pi, HttpURLConnection httpuc)
667 throws IOException {
668 /* If "HTTP/*" is found in the beginning, return true. Let
669 * HttpURLConnection parse the mime header itself.
670 *
671 * If this isn't valid HTTP, then we don't try to parse a header
672 * out of the beginning of the response into the responses,
673 * and instead just queue up the output stream to it's very beginning.
674 * This seems most reasonable, and is what the NN browser does.
675 */
676
677 keepAliveConnections = -1;
678 keepAliveTimeout = 0;
679
680 boolean ret = false;
681 byte[] b = new byte[8];
682
683 try {
684 int nread = 0;
685 serverInput.mark(10);
686 while (nread < 8) {
687 int r = serverInput.read(b, nread, 8 - nread);
688 if (r < 0) {
689 break;
690 }
691 nread += r;
692 }
693 String keep=null;
694 ret = b[0] == 'H' && b[1] == 'T'
695 && b[2] == 'T' && b[3] == 'P' && b[4] == '/' &&
696 b[5] == '1' && b[6] == '.';
697 serverInput.reset();
698 if (ret) { // is valid HTTP - response started w/ "HTTP/1."
699 responses.parseHeader(serverInput);
700
701 // we've finished parsing http headers
702 // check if there are any applicable cookies to set (in cache)
703 CookieHandler cookieHandler = httpuc.getCookieHandler();
704 if (cookieHandler != null) {
705 URI uri = ParseUtil.toURI(url);
706 // NOTE: That cast from Map shouldn't be necessary but
707 // a bug in javac is triggered under certain circumstances
708 // So we do put the cast in as a workaround until
709 // it is resolved.
710 if (uri != null)
711 cookieHandler.put(uri, responses.getHeaders());
712 }
713
714 /* decide if we're keeping alive:
715 * This is a bit tricky. There's a spec, but most current
716 * servers (10/1/96) that support this differ in dialects.
717 * If the server/client misunderstand each other, the
718 * protocol should fall back onto HTTP/1.0, no keep-alive.
719 */
720 if (usingProxy) { // not likely a proxy will return this
721 keep = responses.findValue("Proxy-Connection");
722 }
723 if (keep == null) {
724 keep = responses.findValue("Connection");
725 }
726 if (keep != null && keep.toLowerCase(Locale.US).equals("keep-alive")) {
727 /* some servers, notably Apache1.1, send something like:
728 * "Keep-Alive: timeout=15, max=1" which we should respect.
729 */
730 HeaderParser p = new HeaderParser(
731 responses.findValue("Keep-Alive"));
732 if (p != null) {
733 /* default should be larger in case of proxy */
734 keepAliveConnections = p.findInt("max", usingProxy?50:5);
735 keepAliveTimeout = p.findInt("timeout", usingProxy?60:5);
736 }
737 } else if (b[7] != '0') {
738 /*
739 * We're talking 1.1 or later. Keep persistent until
740 * the server says to close.
741 */
742 if (keep != null) {
743 /*
744 * The only Connection token we understand is close.
745 * Paranoia: if there is any Connection header then
746 * treat as non-persistent.
747 */
748 keepAliveConnections = 1;
749 } else {
750 keepAliveConnections = 5;
751 }
752 }
753 } else if (nread != 8) {
754 if (!failedOnce && requests != null) {
755 failedOnce = true;
756 if (getRequestMethod().equals("CONNECT") ||
757 (httpuc.getRequestMethod().equals("POST") &&
758 (!retryPostProp || streaming))) {
759 // do not retry the request
760 } else {
761 closeServer();
762 cachedHttpClient = false;
763 openServer();
764 if (needsTunneling()) {
765 httpuc.doTunneling();
766 }
767 afterConnect();
768 writeRequests(requests, poster);
769 return parseHTTP(responses, pi, httpuc);
770 }
771 }
772 throw new SocketException("Unexpected end of file from server");
773 } else {
774 // we can't vouche for what this is....
775 responses.set("Content-type", "unknown/unknown");
776 }
777 } catch (IOException e) {
778 throw e;
779 }
780
781 int code = -1;
782 try {
783 String resp;
784 resp = responses.getValue(0);
785 /* should have no leading/trailing LWS
786 * expedite the typical case by assuming it has
787 * form "HTTP/1.x <WS> 2XX <mumble>"
788 */
789 int ind;
790 ind = resp.indexOf(' ');
791 while(resp.charAt(ind) == ' ')
792 ind++;
793 code = Integer.parseInt(resp.substring(ind, ind + 3));
794 } catch (Exception e) {}
795
796 if (code == HTTP_CONTINUE && ignoreContinue) {
797 responses.reset();
798 return parseHTTPHeader(responses, pi, httpuc);
799 }
800
801 long cl = -1;
802
803 /*
804 * Set things up to parse the entity body of the reply.
805 * We should be smarter about avoid pointless work when
806 * the HTTP method and response code indicate there will be
807 * no entity body to parse.
808 */
809 String te = responses.findValue("Transfer-Encoding");
810 if (te != null && te.equalsIgnoreCase("chunked")) {
811 serverInput = new ChunkedInputStream(serverInput, this, responses);
812
813 /*
814 * If keep alive not specified then close after the stream
815 * has completed.
816 */
817 if (keepAliveConnections <= 1) {
818 keepAliveConnections = 1;
819 keepingAlive = false;
820 } else {
821 keepingAlive = true;
822 }
823 failedOnce = false;
824 } else {
825
826 /*
827 * If it's a keep alive connection then we will keep
828 * (alive if :-
829 * 1. content-length is specified, or
830 * 2. "Not-Modified" or "No-Content" responses - RFC 2616 states that
831 * 204 or 304 response must not include a message body.
832 */
833 String cls = responses.findValue("content-length");
834 if (cls != null) {
835 try {
836 cl = Long.parseLong(cls);
837 } catch (NumberFormatException e) {
838 cl = -1;
839 }
840 }
841 String requestLine = requests.getKey(0);
842
843 if ((requestLine != null &&
844 (requestLine.startsWith("HEAD"))) ||
845 code == HttpURLConnection.HTTP_NOT_MODIFIED ||
846 code == HttpURLConnection.HTTP_NO_CONTENT) {
847 cl = 0;
848 }
849
850 if (keepAliveConnections > 1 &&
851 (cl >= 0 ||
852 code == HttpURLConnection.HTTP_NOT_MODIFIED ||
853 code == HttpURLConnection.HTTP_NO_CONTENT)) {
854 keepingAlive = true;
855 failedOnce = false;
856 } else if (keepingAlive) {
857 /* Previously we were keeping alive, and now we're not. Remove
858 * this from the cache (but only here, once) - otherwise we get
859 * multiple removes and the cache count gets messed up.
860 */
861 keepingAlive=false;
862 }
863 }
864
865 /* wrap a KeepAliveStream/MeteredStream around it if appropriate */
866
867 if (cl > 0) {
868 // In this case, content length is well known, so it is okay
869 // to wrap the input stream with KeepAliveStream/MeteredStream.
870
871 if (pi != null) {
872 // Progress monitor is enabled
873 pi.setContentType(responses.findValue("content-type"));
874 }
875
876 if (isKeepingAlive()) {
877 // Wrap KeepAliveStream if keep alive is enabled.
878 logFinest("KeepAlive stream used: " + url);
879 serverInput = new KeepAliveStream(serverInput, pi, cl, this);
880 failedOnce = false;
881 }
882 else {
883 serverInput = new MeteredStream(serverInput, pi, cl);
884 }
885 }
886 else if (cl == -1) {
887 // In this case, content length is unknown - the input
888 // stream would simply be a regular InputStream or
889 // ChunkedInputStream.
890
891 if (pi != null) {
892 // Progress monitoring is enabled.
893
894 pi.setContentType(responses.findValue("content-type"));
895
896 // Wrap MeteredStream for tracking indeterministic
897 // progress, even if the input stream is ChunkedInputStream.
898 serverInput = new MeteredStream(serverInput, pi, cl);
899 }
900 else {
901 // Progress monitoring is disabled, and there is no
902 // need to wrap an unknown length input stream.
903
904 // ** This is an no-op **
905 }
906 }
907 else {
908 if (pi != null)
909 pi.finishTracking();
910 }
911
912 return ret;
913 }
914
915 public synchronized InputStream getInputStream() {
916 return serverInput;
917 }
918
919 public OutputStream getOutputStream() {
920 return serverOutput;
921 }
922
923 @Override
924 public String toString() {
925 return getClass().getName()+"("+url+")";
926 }
927
928 public final boolean isKeepingAlive() {
929 return getHttpKeepAliveSet() && keepingAlive;
930 }
931
932 public void setCacheRequest(CacheRequest cacheRequest) {
933 this.cacheRequest = cacheRequest;
934 }
935
936 CacheRequest getCacheRequest() {
937 return cacheRequest;
938 }
939
940 String getRequestMethod() {
941 if (requests != null) {
942 String requestLine = requests.getKey(0);
943 if (requestLine != null) {
944 return requestLine.split("\\s+")[0];
945 }
946 }
947 return "";
948 }
949
950 @Override
951 protected void finalize() throws Throwable {
952 // This should do nothing. The stream finalizer will
953 // close the fd.
954 }
955
956 public void setDoNotRetry(boolean value) {
957 // failedOnce is used to determine if a request should be retried.
958 failedOnce = value;
959 }
960
961 public void setIgnoreContinue(boolean value) {
962 ignoreContinue = value;
963 }
964
965 /* Use only on connections in error. */
966 @Override
967 public void closeServer() {
968 try {
969 keepingAlive = false;
970 serverSocket.close();
971 } catch (Exception e) {}
972 }
973
974 /**
975 * @return the proxy host being used for this client, or null
976 * if we're not going through a proxy
977 */
978 public String getProxyHostUsed() {
979 if (!usingProxy) {
980 return null;
981 } else {
982 return ((InetSocketAddress)proxy.address()).getHostString();
983 }
984 }
985
986 /**
987 * @return the proxy port being used for this client. Meaningless
988 * if getProxyHostUsed() gives null.
989 */
990 public int getProxyPortUsed() {
991 if (usingProxy)
992 return ((InetSocketAddress)proxy.address()).getPort();
993 return -1;
994 }
995 }
--- EOF ---