Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
PojavLauncherTeam
GitHub Repository: PojavLauncherTeam/jdk17u
Path: blob/master/src/java.base/share/classes/sun/net/www/http/HttpClient.java
67771 views
1
/*
2
* Copyright (c) 1994, 2021, 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 java.util.Objects;
32
import java.util.Properties;
33
import java.util.concurrent.locks.ReentrantLock;
34
35
import sun.net.NetworkClient;
36
import sun.net.ProgressSource;
37
import sun.net.www.MessageHeader;
38
import sun.net.www.HeaderParser;
39
import sun.net.www.MeteredStream;
40
import sun.net.www.ParseUtil;
41
import sun.net.www.protocol.http.AuthenticatorKeys;
42
import sun.net.www.protocol.http.HttpURLConnection;
43
import sun.util.logging.PlatformLogger;
44
import static sun.net.www.protocol.http.HttpURLConnection.TunnelState.*;
45
import sun.security.action.GetPropertyAction;
46
47
/**
48
* @author Herb Jellinek
49
* @author Dave Brown
50
*/
51
public class HttpClient extends NetworkClient {
52
private final ReentrantLock clientLock = new ReentrantLock();
53
54
// whether this httpclient comes from the cache
55
protected boolean cachedHttpClient = false;
56
57
protected boolean inCache;
58
59
// Http requests we send
60
MessageHeader requests;
61
62
// Http data we send with the headers
63
PosterOutputStream poster = null;
64
65
// true if we are in streaming mode (fixed length or chunked)
66
boolean streaming;
67
68
// if we've had one io error
69
boolean failedOnce = false;
70
71
/** Response code for CONTINUE */
72
private boolean ignoreContinue = true;
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
private static 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
/* All proxying (generic as well as instance-specific) may be
90
* disabled through use of this flag
91
*/
92
protected boolean proxyDisabled;
93
94
// are we using proxy in this instance?
95
public boolean usingProxy = false;
96
// target host, port for the URL
97
protected String host;
98
protected int port;
99
100
/* where we cache currently open, persistent connections */
101
protected static KeepAliveCache kac = new KeepAliveCache();
102
103
private static boolean keepAliveProp = true;
104
105
// retryPostProp is true by default so as to preserve behavior
106
// from previous releases.
107
private static boolean retryPostProp = true;
108
109
/* Value of the system property jdk.ntlm.cache;
110
if false, then NTLM connections will not be cached.
111
The default value is 'true'. */
112
private static final boolean cacheNTLMProp;
113
/* Value of the system property jdk.spnego.cache;
114
if false, then connections authentified using the Negotiate/Kerberos
115
scheme will not be cached.
116
The default value is 'true'. */
117
private static final boolean cacheSPNEGOProp;
118
119
volatile boolean keepingAlive; /* this is a keep-alive connection */
120
volatile boolean disableKeepAlive;/* keep-alive has been disabled for this
121
connection - this will be used when
122
recomputing the value of keepingAlive */
123
int keepAliveConnections = -1; /* number of keep-alives left */
124
125
/*
126
* The timeout if specified by the server. Following values possible
127
* 0: the server specified no keep alive headers
128
* -1: the server provided "Connection: keep-alive" but did not specify a
129
* a particular time in a "Keep-Alive:" headers
130
* Positive values are the number of seconds specified by the server
131
* in a "Keep-Alive" header
132
*/
133
int keepAliveTimeout = 0;
134
135
/** whether the response is to be cached */
136
private CacheRequest cacheRequest = null;
137
138
/** Url being fetched. */
139
protected URL url;
140
141
/* if set, the client will be reused and must not be put in cache */
142
public boolean reuse = false;
143
144
// Traffic capture tool, if configured. See HttpCapture class for info
145
private HttpCapture capture = null;
146
147
/* "jdk.https.negotiate.cbt" property can be set to "always" (always sent), "never" (never sent) or
148
* "domain:a,c.d,*.e.f" (sent to host a, or c.d or to the domain e.f and any of its subdomains). This is
149
* a comma separated list of arbitrary length with no white-space allowed.
150
* If enabled (for a particular destination) then Negotiate/SPNEGO authentication requests will include
151
* a channel binding token for the destination server. The default behavior and setting for the
152
* property is "never"
153
*/
154
private static final String spnegoCBT;
155
156
private static final PlatformLogger logger = HttpURLConnection.getHttpLogger();
157
158
private static void logFinest(String msg) {
159
if (logger.isLoggable(PlatformLogger.Level.FINEST)) {
160
logger.finest(msg);
161
}
162
}
163
private static void logError(String msg) {
164
if (logger.isLoggable(PlatformLogger.Level.SEVERE)) {
165
logger.severe(msg);
166
}
167
}
168
169
protected volatile String authenticatorKey;
170
171
/**
172
* A NOP method kept for backwards binary compatibility
173
* @deprecated -- system properties are no longer cached.
174
*/
175
@Deprecated
176
public static synchronized void resetProperties() {
177
}
178
179
int getKeepAliveTimeout() {
180
return keepAliveTimeout;
181
}
182
183
static String normalizeCBT(String s) {
184
if (s == null || s.equals("never")) {
185
return "never";
186
}
187
if (s.equals("always") || s.startsWith("domain:")) {
188
return s;
189
} else {
190
logError("Unexpected value for \"jdk.https.negotiate.cbt\" system property");
191
return "never";
192
}
193
}
194
195
static {
196
Properties props = GetPropertyAction.privilegedGetProperties();
197
String keepAlive = props.getProperty("http.keepAlive");
198
String retryPost = props.getProperty("sun.net.http.retryPost");
199
String cacheNTLM = props.getProperty("jdk.ntlm.cache");
200
String cacheSPNEGO = props.getProperty("jdk.spnego.cache");
201
202
String s = props.getProperty("jdk.https.negotiate.cbt");
203
spnegoCBT = normalizeCBT(s);
204
205
if (keepAlive != null) {
206
keepAliveProp = Boolean.parseBoolean(keepAlive);
207
} else {
208
keepAliveProp = true;
209
}
210
211
if (retryPost != null) {
212
retryPostProp = Boolean.parseBoolean(retryPost);
213
} else {
214
retryPostProp = true;
215
}
216
217
if (cacheNTLM != null) {
218
cacheNTLMProp = Boolean.parseBoolean(cacheNTLM);
219
} else {
220
cacheNTLMProp = true;
221
}
222
223
if (cacheSPNEGO != null) {
224
cacheSPNEGOProp = Boolean.parseBoolean(cacheSPNEGO);
225
} else {
226
cacheSPNEGOProp = true;
227
}
228
}
229
230
/**
231
* @return true iff http keep alive is set (i.e. enabled). Defaults
232
* to true if the system property http.keepAlive isn't set.
233
*/
234
public boolean getHttpKeepAliveSet() {
235
return keepAliveProp;
236
}
237
238
public String getSpnegoCBT() {
239
return spnegoCBT;
240
}
241
242
protected HttpClient() {
243
}
244
245
private HttpClient(URL url)
246
throws IOException {
247
this(url, (String)null, -1, false);
248
}
249
250
protected HttpClient(URL url,
251
boolean proxyDisabled) throws IOException {
252
this(url, null, -1, proxyDisabled);
253
}
254
255
/* This package-only CTOR should only be used for FTP piggy-backed on HTTP
256
* URL's that use this won't take advantage of keep-alive.
257
* Additionally, this constructor may be used as a last resort when the
258
* first HttpClient gotten through New() failed (probably b/c of a
259
* Keep-Alive mismatch).
260
*
261
* XXX That documentation is wrong ... it's not package-private any more
262
*/
263
public HttpClient(URL url, String proxyHost, int proxyPort)
264
throws IOException {
265
this(url, proxyHost, proxyPort, false);
266
}
267
268
protected HttpClient(URL url, Proxy p, int to) throws IOException {
269
proxy = (p == null) ? Proxy.NO_PROXY : p;
270
this.host = url.getHost();
271
this.url = url;
272
port = url.getPort();
273
if (port == -1) {
274
port = getDefaultPort();
275
}
276
setConnectTimeout(to);
277
278
capture = HttpCapture.getCapture(url);
279
openServer();
280
}
281
282
protected static Proxy newHttpProxy(String proxyHost, int proxyPort,
283
String proto) {
284
if (proxyHost == null || proto == null)
285
return Proxy.NO_PROXY;
286
int pport = proxyPort < 0 ? getDefaultPort(proto) : proxyPort;
287
InetSocketAddress saddr = InetSocketAddress.createUnresolved(proxyHost, pport);
288
return new Proxy(Proxy.Type.HTTP, saddr);
289
}
290
291
/*
292
* This constructor gives "ultimate" flexibility, including the ability
293
* to bypass implicit proxying. Sometimes we need to be using tunneling
294
* (transport or network level) instead of proxying (application level),
295
* for example when we don't want the application level data to become
296
* visible to third parties.
297
*
298
* @param url the URL to which we're connecting
299
* @param proxy proxy to use for this URL (e.g. forwarding)
300
* @param proxyPort proxy port to use for this URL
301
* @param proxyDisabled true to disable default proxying
302
*/
303
private HttpClient(URL url, String proxyHost, int proxyPort,
304
boolean proxyDisabled)
305
throws IOException {
306
this(url, proxyDisabled ? Proxy.NO_PROXY :
307
newHttpProxy(proxyHost, proxyPort, "http"), -1);
308
}
309
310
public HttpClient(URL url, String proxyHost, int proxyPort,
311
boolean proxyDisabled, int to)
312
throws IOException {
313
this(url, proxyDisabled ? Proxy.NO_PROXY :
314
newHttpProxy(proxyHost, proxyPort, "http"), to);
315
}
316
317
/* This class has no public constructor for HTTP. This method is used to
318
* get an HttpClient to the specified URL. If there's currently an
319
* active HttpClient to that server/port, you'll get that one.
320
*/
321
public static HttpClient New(URL url)
322
throws IOException {
323
return HttpClient.New(url, Proxy.NO_PROXY, -1, true, null);
324
}
325
326
public static HttpClient New(URL url, boolean useCache)
327
throws IOException {
328
return HttpClient.New(url, Proxy.NO_PROXY, -1, useCache, null);
329
}
330
331
public static HttpClient New(URL url, Proxy p, int to, boolean useCache,
332
HttpURLConnection httpuc) throws IOException
333
{
334
if (p == null) {
335
p = Proxy.NO_PROXY;
336
}
337
HttpClient ret = null;
338
/* see if one's already around */
339
if (useCache) {
340
ret = kac.get(url, null);
341
if (ret != null && httpuc != null &&
342
httpuc.streaming() &&
343
"POST".equals(httpuc.getRequestMethod())) {
344
if (!ret.available()) {
345
ret.inCache = false;
346
ret.closeServer();
347
ret = null;
348
}
349
}
350
if (ret != null) {
351
String ak = httpuc == null ? AuthenticatorKeys.DEFAULT
352
: httpuc.getAuthenticatorKey();
353
boolean compatible = Objects.equals(ret.proxy, p)
354
&& Objects.equals(ret.getAuthenticatorKey(), ak);
355
if (compatible) {
356
ret.lock();
357
try {
358
ret.cachedHttpClient = true;
359
assert ret.inCache;
360
ret.inCache = false;
361
if (httpuc != null && ret.needsTunneling())
362
httpuc.setTunnelState(TUNNELING);
363
logFinest("KeepAlive stream retrieved from the cache, " + ret);
364
} finally {
365
ret.unlock();
366
}
367
} else {
368
// We cannot return this connection to the cache as it's
369
// KeepAliveTimeout will get reset. We simply close the connection.
370
// This should be fine as it is very rare that a connection
371
// to the same host will not use the same proxy.
372
ret.lock();
373
try {
374
ret.inCache = false;
375
ret.closeServer();
376
} finally {
377
ret.unlock();
378
}
379
ret = null;
380
}
381
}
382
}
383
if (ret == null) {
384
ret = new HttpClient(url, p, to);
385
if (httpuc != null) {
386
ret.authenticatorKey = httpuc.getAuthenticatorKey();
387
}
388
} else {
389
@SuppressWarnings("removal")
390
SecurityManager security = System.getSecurityManager();
391
if (security != null) {
392
if (ret.proxy == Proxy.NO_PROXY || ret.proxy == null) {
393
security.checkConnect(InetAddress.getByName(url.getHost()).getHostAddress(), url.getPort());
394
} else {
395
security.checkConnect(url.getHost(), url.getPort());
396
}
397
}
398
ret.url = url;
399
}
400
return ret;
401
}
402
403
public static HttpClient New(URL url, Proxy p, int to,
404
HttpURLConnection httpuc) throws IOException
405
{
406
return New(url, p, to, true, httpuc);
407
}
408
409
public static HttpClient New(URL url, String proxyHost, int proxyPort,
410
boolean useCache)
411
throws IOException {
412
return New(url, newHttpProxy(proxyHost, proxyPort, "http"),
413
-1, useCache, null);
414
}
415
416
public static HttpClient New(URL url, String proxyHost, int proxyPort,
417
boolean useCache, int to,
418
HttpURLConnection httpuc)
419
throws IOException {
420
return New(url, newHttpProxy(proxyHost, proxyPort, "http"),
421
to, useCache, httpuc);
422
}
423
424
public final String getAuthenticatorKey() {
425
String k = authenticatorKey;
426
if (k == null) return AuthenticatorKeys.DEFAULT;
427
return k;
428
}
429
430
/* return it to the cache as still usable, if:
431
* 1) It's keeping alive, AND
432
* 2) It still has some connections left, AND
433
* 3) It hasn't had a error (PrintStream.checkError())
434
* 4) It hasn't timed out
435
*
436
* If this client is not keepingAlive, it should have been
437
* removed from the cache in the parseHeaders() method.
438
*/
439
440
public void finished() {
441
if (reuse) /* will be reused */
442
return;
443
keepAliveConnections--;
444
poster = null;
445
if (keepAliveConnections > 0 && isKeepingAlive() &&
446
!(serverOutput.checkError())) {
447
/* This connection is keepingAlive && still valid.
448
* Return it to the cache.
449
*/
450
putInKeepAliveCache();
451
} else {
452
closeServer();
453
}
454
}
455
456
protected boolean available() {
457
boolean available = true;
458
int old = -1;
459
460
lock();
461
try {
462
try {
463
old = serverSocket.getSoTimeout();
464
serverSocket.setSoTimeout(1);
465
BufferedInputStream tmpbuf =
466
new BufferedInputStream(serverSocket.getInputStream());
467
int r = tmpbuf.read();
468
if (r == -1) {
469
logFinest("HttpClient.available(): " +
470
"read returned -1: not available");
471
available = false;
472
}
473
} catch (SocketTimeoutException e) {
474
logFinest("HttpClient.available(): " +
475
"SocketTimeout: its available");
476
} finally {
477
if (old != -1)
478
serverSocket.setSoTimeout(old);
479
}
480
} catch (IOException e) {
481
logFinest("HttpClient.available(): " +
482
"SocketException: not available");
483
available = false;
484
} finally {
485
unlock();
486
}
487
return available;
488
}
489
490
protected void putInKeepAliveCache() {
491
lock();
492
try {
493
if (inCache) {
494
assert false : "Duplicate put to keep alive cache";
495
return;
496
}
497
inCache = true;
498
kac.put(url, null, this);
499
} finally {
500
unlock();
501
}
502
}
503
504
protected boolean isInKeepAliveCache() {
505
lock();
506
try {
507
return inCache;
508
} finally {
509
unlock();
510
}
511
}
512
513
/*
514
* Close an idle connection to this URL (if it exists in the
515
* cache).
516
*/
517
public void closeIdleConnection() {
518
HttpClient http = kac.get(url, null);
519
if (http != null) {
520
http.closeServer();
521
}
522
}
523
524
/* We're very particular here about what our InputStream to the server
525
* looks like for reasons that are apparent if you can decipher the
526
* method parseHTTP(). That's why this method is overidden from the
527
* superclass.
528
*/
529
@Override
530
public void openServer(String server, int port) throws IOException {
531
serverSocket = doConnect(server, port);
532
try {
533
OutputStream out = serverSocket.getOutputStream();
534
if (capture != null) {
535
out = new HttpCaptureOutputStream(out, capture);
536
}
537
serverOutput = new PrintStream(
538
new BufferedOutputStream(out),
539
false, encoding);
540
} catch (UnsupportedEncodingException e) {
541
throw new InternalError(encoding+" encoding not found", e);
542
}
543
serverSocket.setTcpNoDelay(true);
544
}
545
546
/*
547
* Returns true if the http request should be tunneled through proxy.
548
* An example where this is the case is Https.
549
*/
550
public boolean needsTunneling() {
551
return false;
552
}
553
554
/*
555
* Returns true if this httpclient is from cache
556
*/
557
public boolean isCachedConnection() {
558
lock();
559
try {
560
return cachedHttpClient;
561
} finally {
562
unlock();
563
}
564
}
565
566
/*
567
* Finish any work left after the socket connection is
568
* established. In the normal http case, it's a NO-OP. Subclass
569
* may need to override this. An example is Https, where for
570
* direct connection to the origin server, ssl handshake needs to
571
* be done; for proxy tunneling, the socket needs to be converted
572
* into an SSL socket before ssl handshake can take place.
573
*/
574
public void afterConnect() throws IOException, UnknownHostException {
575
// NO-OP. Needs to be overwritten by HttpsClient
576
}
577
578
/*
579
* call openServer in a privileged block
580
*/
581
@SuppressWarnings("removal")
582
private void privilegedOpenServer(final InetSocketAddress server)
583
throws IOException
584
{
585
assert clientLock.isHeldByCurrentThread();
586
try {
587
java.security.AccessController.doPrivileged(
588
new java.security.PrivilegedExceptionAction<>() {
589
public Void run() throws IOException {
590
openServer(server.getHostString(), server.getPort());
591
return null;
592
}
593
});
594
} catch (java.security.PrivilegedActionException pae) {
595
throw (IOException) pae.getException();
596
}
597
}
598
599
/*
600
* call super.openServer
601
*/
602
private void superOpenServer(final String proxyHost,
603
final int proxyPort)
604
throws IOException, UnknownHostException
605
{
606
super.openServer(proxyHost, proxyPort);
607
}
608
609
/*
610
*/
611
protected void openServer() throws IOException {
612
613
@SuppressWarnings("removal")
614
SecurityManager security = System.getSecurityManager();
615
616
lock();
617
try {
618
if (security != null) {
619
security.checkConnect(host, port);
620
}
621
622
if (keepingAlive) { // already opened
623
return;
624
}
625
626
if (url.getProtocol().equals("http") ||
627
url.getProtocol().equals("https")) {
628
629
if ((proxy != null) && (proxy.type() == Proxy.Type.HTTP)) {
630
sun.net.www.URLConnection.setProxiedHost(host);
631
privilegedOpenServer((InetSocketAddress) proxy.address());
632
usingProxy = true;
633
return;
634
} else {
635
// make direct connection
636
openServer(host, port);
637
usingProxy = false;
638
return;
639
}
640
641
} else {
642
/* we're opening some other kind of url, most likely an
643
* ftp url.
644
*/
645
if ((proxy != null) && (proxy.type() == Proxy.Type.HTTP)) {
646
sun.net.www.URLConnection.setProxiedHost(host);
647
privilegedOpenServer((InetSocketAddress) proxy.address());
648
usingProxy = true;
649
return;
650
} else {
651
// make direct connection
652
super.openServer(host, port);
653
usingProxy = false;
654
return;
655
}
656
}
657
} finally {
658
unlock();
659
}
660
}
661
662
public String getURLFile() throws IOException {
663
664
String fileName;
665
666
/**
667
* proxyDisabled is set by subclass HttpsClient!
668
*/
669
if (usingProxy && !proxyDisabled) {
670
// Do not use URLStreamHandler.toExternalForm as the fragment
671
// should not be part of the RequestURI. It should be an
672
// absolute URI which does not have a fragment part.
673
StringBuilder result = new StringBuilder(128);
674
result.append(url.getProtocol());
675
result.append(":");
676
if (url.getAuthority() != null && !url.getAuthority().isEmpty()) {
677
result.append("//");
678
result.append(url.getAuthority());
679
}
680
if (url.getPath() != null) {
681
result.append(url.getPath());
682
}
683
if (url.getQuery() != null) {
684
result.append('?');
685
result.append(url.getQuery());
686
}
687
688
fileName = result.toString();
689
} else {
690
fileName = url.getFile();
691
692
if ((fileName == null) || (fileName.isEmpty())) {
693
fileName = "/";
694
} else if (fileName.charAt(0) == '?') {
695
/* HTTP/1.1 spec says in 5.1.2. about Request-URI:
696
* "Note that the absolute path cannot be empty; if
697
* none is present in the original URI, it MUST be
698
* given as "/" (the server root)." So if the file
699
* name here has only a query string, the path is
700
* empty and we also have to add a "/".
701
*/
702
fileName = "/" + fileName;
703
}
704
}
705
706
if (fileName.indexOf('\n') == -1)
707
return fileName;
708
else
709
throw new java.net.MalformedURLException("Illegal character in URL");
710
}
711
712
/**
713
* @deprecated
714
*/
715
@Deprecated
716
public void writeRequests(MessageHeader head) {
717
requests = head;
718
requests.print(serverOutput);
719
serverOutput.flush();
720
}
721
722
public void writeRequests(MessageHeader head,
723
PosterOutputStream pos) throws IOException {
724
requests = head;
725
requests.print(serverOutput);
726
poster = pos;
727
if (poster != null)
728
poster.writeTo(serverOutput);
729
serverOutput.flush();
730
}
731
732
public void writeRequests(MessageHeader head,
733
PosterOutputStream pos,
734
boolean streaming) throws IOException {
735
this.streaming = streaming;
736
writeRequests(head, pos);
737
}
738
739
/** Parse the first line of the HTTP request. It usually looks
740
something like: {@literal "HTTP/1.0 <number> comment\r\n"}. */
741
742
public boolean parseHTTP(MessageHeader responses, ProgressSource pi, HttpURLConnection httpuc)
743
throws IOException {
744
/* If "HTTP/*" is found in the beginning, return true. Let
745
* HttpURLConnection parse the mime header itself.
746
*
747
* If this isn't valid HTTP, then we don't try to parse a header
748
* out of the beginning of the response into the responses,
749
* and instead just queue up the output stream to it's very beginning.
750
* This seems most reasonable, and is what the NN browser does.
751
*/
752
753
try {
754
serverInput = serverSocket.getInputStream();
755
if (capture != null) {
756
serverInput = new HttpCaptureInputStream(serverInput, capture);
757
}
758
serverInput = new BufferedInputStream(serverInput);
759
return (parseHTTPHeader(responses, pi, httpuc));
760
} catch (SocketTimeoutException stex) {
761
// We don't want to retry the request when the app. sets a timeout
762
// but don't close the server if timeout while waiting for 100-continue
763
if (ignoreContinue) {
764
closeServer();
765
}
766
throw stex;
767
} catch (IOException e) {
768
closeServer();
769
cachedHttpClient = false;
770
if (!failedOnce && requests != null) {
771
failedOnce = true;
772
if (getRequestMethod().equals("CONNECT")
773
|| streaming
774
|| (httpuc.getRequestMethod().equals("POST")
775
&& !retryPostProp)) {
776
// do not retry the request
777
} else {
778
// try once more
779
openServer();
780
checkTunneling(httpuc);
781
afterConnect();
782
writeRequests(requests, poster);
783
return parseHTTP(responses, pi, httpuc);
784
}
785
}
786
throw e;
787
}
788
789
}
790
791
// Check whether tunnel must be open and open it if necessary
792
// (in the case of HTTPS with proxy)
793
private void checkTunneling(HttpURLConnection httpuc) throws IOException {
794
if (needsTunneling()) {
795
MessageHeader origRequests = requests;
796
PosterOutputStream origPoster = poster;
797
httpuc.doTunneling();
798
requests = origRequests;
799
poster = origPoster;
800
}
801
}
802
803
private boolean parseHTTPHeader(MessageHeader responses, ProgressSource pi, HttpURLConnection httpuc)
804
throws IOException {
805
/* If "HTTP/*" is found in the beginning, return true. Let
806
* HttpURLConnection parse the mime header itself.
807
*
808
* If this isn't valid HTTP, then we don't try to parse a header
809
* out of the beginning of the response into the responses,
810
* and instead just queue up the output stream to it's very beginning.
811
* This seems most reasonable, and is what the NN browser does.
812
*/
813
814
keepAliveConnections = -1;
815
keepAliveTimeout = 0;
816
817
boolean ret = false;
818
byte[] b = new byte[8];
819
820
try {
821
int nread = 0;
822
serverInput.mark(10);
823
while (nread < 8) {
824
int r = serverInput.read(b, nread, 8 - nread);
825
if (r < 0) {
826
break;
827
}
828
nread += r;
829
}
830
String keep=null;
831
String authenticate=null;
832
ret = b[0] == 'H' && b[1] == 'T'
833
&& b[2] == 'T' && b[3] == 'P' && b[4] == '/' &&
834
b[5] == '1' && b[6] == '.';
835
serverInput.reset();
836
if (ret) { // is valid HTTP - response started w/ "HTTP/1."
837
responses.parseHeader(serverInput);
838
839
// we've finished parsing http headers
840
// check if there are any applicable cookies to set (in cache)
841
CookieHandler cookieHandler = httpuc.getCookieHandler();
842
if (cookieHandler != null) {
843
URI uri = ParseUtil.toURI(url);
844
// NOTE: That cast from Map shouldn't be necessary but
845
// a bug in javac is triggered under certain circumstances
846
// So we do put the cast in as a workaround until
847
// it is resolved.
848
if (uri != null)
849
cookieHandler.put(uri, responses.getHeaders());
850
}
851
852
/* decide if we're keeping alive:
853
* This is a bit tricky. There's a spec, but most current
854
* servers (10/1/96) that support this differ in dialects.
855
* If the server/client misunderstand each other, the
856
* protocol should fall back onto HTTP/1.0, no keep-alive.
857
*/
858
if (usingProxy) { // not likely a proxy will return this
859
keep = responses.findValue("Proxy-Connection");
860
authenticate = responses.findValue("Proxy-Authenticate");
861
}
862
if (keep == null) {
863
keep = responses.findValue("Connection");
864
authenticate = responses.findValue("WWW-Authenticate");
865
}
866
867
// 'disableKeepAlive' starts with the value false.
868
// It can transition from false to true, but once true
869
// it stays true.
870
// If cacheNTLMProp is false, and disableKeepAlive is false,
871
// then we need to examine the response headers to figure out
872
// whether we are doing NTLM authentication. If we do NTLM,
873
// and cacheNTLMProp is false, than we can't keep this connection
874
// alive: we will switch disableKeepAlive to true.
875
boolean canKeepAlive = !disableKeepAlive;
876
if (canKeepAlive && (cacheNTLMProp == false || cacheSPNEGOProp == false)
877
&& authenticate != null) {
878
authenticate = authenticate.toLowerCase(Locale.US);
879
if (cacheNTLMProp == false) {
880
canKeepAlive &= !authenticate.startsWith("ntlm ");
881
}
882
if (cacheSPNEGOProp == false) {
883
canKeepAlive &= !authenticate.startsWith("negotiate ");
884
canKeepAlive &= !authenticate.startsWith("kerberos ");
885
}
886
}
887
disableKeepAlive |= !canKeepAlive;
888
889
if (keep != null && keep.toLowerCase(Locale.US).equals("keep-alive")) {
890
/* some servers, notably Apache1.1, send something like:
891
* "Keep-Alive: timeout=15, max=1" which we should respect.
892
*/
893
if (disableKeepAlive) {
894
keepAliveConnections = 1;
895
} else {
896
HeaderParser p = new HeaderParser(
897
responses.findValue("Keep-Alive"));
898
/* default should be larger in case of proxy */
899
keepAliveConnections = p.findInt("max", usingProxy?50:5);
900
keepAliveTimeout = p.findInt("timeout", -1);
901
}
902
} else if (b[7] != '0') {
903
/*
904
* We're talking 1.1 or later. Keep persistent until
905
* the server says to close.
906
*/
907
if (keep != null || disableKeepAlive) {
908
/*
909
* The only Connection token we understand is close.
910
* Paranoia: if there is any Connection header then
911
* treat as non-persistent.
912
*/
913
keepAliveConnections = 1;
914
} else {
915
keepAliveConnections = 5;
916
}
917
}
918
} else if (nread != 8) {
919
if (!failedOnce && requests != null) {
920
failedOnce = true;
921
if (getRequestMethod().equals("CONNECT")
922
|| streaming
923
|| (httpuc.getRequestMethod().equals("POST")
924
&& !retryPostProp)) {
925
// do not retry the request
926
} else {
927
closeServer();
928
cachedHttpClient = false;
929
openServer();
930
checkTunneling(httpuc);
931
afterConnect();
932
writeRequests(requests, poster);
933
return parseHTTP(responses, pi, httpuc);
934
}
935
}
936
throw new SocketException("Unexpected end of file from server");
937
} else {
938
// we can't vouche for what this is....
939
responses.set("Content-type", "unknown/unknown");
940
}
941
} catch (IOException e) {
942
throw e;
943
}
944
945
int code = -1;
946
try {
947
String resp;
948
resp = responses.getValue(0);
949
/* should have no leading/trailing LWS
950
* expedite the typical case by assuming it has
951
* form "HTTP/1.x <WS> 2XX <mumble>"
952
*/
953
int ind;
954
ind = resp.indexOf(' ');
955
while(resp.charAt(ind) == ' ')
956
ind++;
957
code = Integer.parseInt(resp, ind, ind + 3, 10);
958
} catch (Exception e) {}
959
960
if (code == HTTP_CONTINUE && ignoreContinue) {
961
responses.reset();
962
return parseHTTPHeader(responses, pi, httpuc);
963
}
964
965
long cl = -1;
966
967
/*
968
* Set things up to parse the entity body of the reply.
969
* We should be smarter about avoid pointless work when
970
* the HTTP method and response code indicate there will be
971
* no entity body to parse.
972
*/
973
String te = responses.findValue("Transfer-Encoding");
974
if (te != null && te.equalsIgnoreCase("chunked")) {
975
serverInput = new ChunkedInputStream(serverInput, this, responses);
976
977
/*
978
* If keep alive not specified then close after the stream
979
* has completed.
980
*/
981
if (keepAliveConnections <= 1) {
982
keepAliveConnections = 1;
983
keepingAlive = false;
984
} else {
985
keepingAlive = !disableKeepAlive;
986
}
987
failedOnce = false;
988
} else {
989
990
/*
991
* If it's a keep alive connection then we will keep
992
* (alive if :-
993
* 1. content-length is specified, or
994
* 2. "Not-Modified" or "No-Content" responses - RFC 2616 states that
995
* 204 or 304 response must not include a message body.
996
*/
997
String cls = responses.findValue("content-length");
998
if (cls != null) {
999
try {
1000
cl = Long.parseLong(cls);
1001
} catch (NumberFormatException e) {
1002
cl = -1;
1003
}
1004
}
1005
String requestLine = requests.getKey(0);
1006
1007
if ((requestLine != null &&
1008
(requestLine.startsWith("HEAD"))) ||
1009
code == HttpURLConnection.HTTP_NOT_MODIFIED ||
1010
code == HttpURLConnection.HTTP_NO_CONTENT) {
1011
cl = 0;
1012
}
1013
1014
if (keepAliveConnections > 1 &&
1015
(cl >= 0 ||
1016
code == HttpURLConnection.HTTP_NOT_MODIFIED ||
1017
code == HttpURLConnection.HTTP_NO_CONTENT)) {
1018
keepingAlive = !disableKeepAlive;
1019
failedOnce = false;
1020
} else if (keepingAlive) {
1021
/* Previously we were keeping alive, and now we're not. Remove
1022
* this from the cache (but only here, once) - otherwise we get
1023
* multiple removes and the cache count gets messed up.
1024
*/
1025
keepingAlive=false;
1026
}
1027
}
1028
1029
/* wrap a KeepAliveStream/MeteredStream around it if appropriate */
1030
1031
if (cl > 0) {
1032
// In this case, content length is well known, so it is okay
1033
// to wrap the input stream with KeepAliveStream/MeteredStream.
1034
1035
if (pi != null) {
1036
// Progress monitor is enabled
1037
pi.setContentType(responses.findValue("content-type"));
1038
}
1039
1040
// If disableKeepAlive == true, the client will not be returned
1041
// to the cache. But we still need to use a keepalive stream to
1042
// allow the multi-message authentication exchange on the connection
1043
boolean useKeepAliveStream = isKeepingAlive() || disableKeepAlive;
1044
if (useKeepAliveStream) {
1045
// Wrap KeepAliveStream if keep alive is enabled.
1046
logFinest("KeepAlive stream used: " + url);
1047
serverInput = new KeepAliveStream(serverInput, pi, cl, this);
1048
failedOnce = false;
1049
}
1050
else {
1051
serverInput = new MeteredStream(serverInput, pi, cl);
1052
}
1053
}
1054
else if (cl == -1) {
1055
// In this case, content length is unknown - the input
1056
// stream would simply be a regular InputStream or
1057
// ChunkedInputStream.
1058
1059
if (pi != null) {
1060
// Progress monitoring is enabled.
1061
1062
pi.setContentType(responses.findValue("content-type"));
1063
1064
// Wrap MeteredStream for tracking indeterministic
1065
// progress, even if the input stream is ChunkedInputStream.
1066
serverInput = new MeteredStream(serverInput, pi, cl);
1067
}
1068
else {
1069
// Progress monitoring is disabled, and there is no
1070
// need to wrap an unknown length input stream.
1071
1072
// ** This is an no-op **
1073
}
1074
}
1075
else {
1076
if (pi != null)
1077
pi.finishTracking();
1078
}
1079
1080
return ret;
1081
}
1082
1083
public InputStream getInputStream() {
1084
lock();
1085
try {
1086
return serverInput;
1087
} finally {
1088
unlock();
1089
}
1090
}
1091
1092
public OutputStream getOutputStream() {
1093
return serverOutput;
1094
}
1095
1096
@Override
1097
public String toString() {
1098
return getClass().getName()+"("+url+")";
1099
}
1100
1101
public final boolean isKeepingAlive() {
1102
return getHttpKeepAliveSet() && keepingAlive;
1103
}
1104
1105
public void setCacheRequest(CacheRequest cacheRequest) {
1106
this.cacheRequest = cacheRequest;
1107
}
1108
1109
CacheRequest getCacheRequest() {
1110
return cacheRequest;
1111
}
1112
1113
String getRequestMethod() {
1114
if (requests != null) {
1115
String requestLine = requests.getKey(0);
1116
if (requestLine != null) {
1117
return requestLine.split("\\s+")[0];
1118
}
1119
}
1120
return "";
1121
}
1122
1123
public void setDoNotRetry(boolean value) {
1124
// failedOnce is used to determine if a request should be retried.
1125
failedOnce = value;
1126
}
1127
1128
public void setIgnoreContinue(boolean value) {
1129
ignoreContinue = value;
1130
}
1131
1132
/* Use only on connections in error. */
1133
@Override
1134
public void closeServer() {
1135
try {
1136
keepingAlive = false;
1137
serverSocket.close();
1138
} catch (Exception e) {}
1139
}
1140
1141
/**
1142
* @return the proxy host being used for this client, or null
1143
* if we're not going through a proxy
1144
*/
1145
public String getProxyHostUsed() {
1146
if (!usingProxy) {
1147
return null;
1148
} else {
1149
return ((InetSocketAddress)proxy.address()).getHostString();
1150
}
1151
}
1152
1153
public boolean getUsingProxy() {
1154
return usingProxy;
1155
}
1156
1157
/**
1158
* @return the proxy port being used for this client. Meaningless
1159
* if getProxyHostUsed() gives null.
1160
*/
1161
public int getProxyPortUsed() {
1162
if (usingProxy)
1163
return ((InetSocketAddress)proxy.address()).getPort();
1164
return -1;
1165
}
1166
1167
public final void lock() {
1168
clientLock.lock();
1169
}
1170
1171
public final void unlock() {
1172
clientLock.unlock();
1173
}
1174
}
1175
1176