-
Notifications
You must be signed in to change notification settings - Fork 20
Expand file tree
/
Copy pathHttpURLConnection.java
More file actions
814 lines (691 loc) · 22.4 KB
/
HttpURLConnection.java
File metadata and controls
814 lines (691 loc) · 22.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
/*
* @(#)HttpURLConnection.java 0.3-3 06/05/2001
*
* This file is part of the HTTPClient package
* Copyright (C) 1996-2001 Ronald Tschal�r
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free
* Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
* MA 02111-1307, USA
*
* For questions, suggestions, bug-reports, enhancement-requests etc.
* I may be contacted at:
*
* ronald@innovation.ch
*
* The HTTPClient's home page is located at:
*
* http://www.innovation.ch/java/HTTPClient/
*
*/
package HTTPClient;
import java.net.URL;
import java.net.ProtocolException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.ByteArrayOutputStream;
import java.util.Date;
import java.util.Hashtable;
import java.util.Enumeration;
/**
* This class is a wrapper around HTTPConnection providing the interface
* defined by java.net.URLConnection and java.net.HttpURLConnection.
*
* <P>This class can be used to replace the HttpClient in the JDK with this
* HTTPClient by defining the property
* <code>java.protocol.handler.pkgs=HTTPClient</code>.
*
* <P>One difference between Sun's HttpClient and this one is that this
* one will provide you with a real output stream if possible. This leads
* to two changes: you should set the request property "Content-Length",
* if possible, before invoking getOutputStream(); and in many cases
* getOutputStream() implies connect(). This should be transparent, though,
* apart from the fact that you can't change any headers or other settings
* anymore once you've gotten the output stream.
* So, for large data do:
* <PRE>
* HttpURLConnection con = (HttpURLConnection) url.openConnection();
*
* con.setDoOutput(true);
* con.setRequestProperty("Content-Length", ...);
* OutputStream out = con.getOutputStream();
*
* out.write(...);
* out.close();
*
* if (con.getResponseCode() != 200)
* ...
* </PRE>
*
* <P>The HTTPClient will send the request data using the chunked transfer
* encoding when no Content-Length is specified and the server is HTTP/1.1
* compatible. Because cgi-scripts can't usually handle this, you may
* experience problems trying to POST data. For this reason, whenever
* the Content-Type is application/x-www-form-urlencoded getOutputStream()
* will buffer the data before sending it so as prevent chunking. If you
* are sending requests with a different Content-Type and are experiencing
* problems then you may want to try setting the system property
* <var>HTTPClient.dontChunkRequests</var> to <var>true</var> (this needs
* to be done either on the command line or somewhere in the code before
* the first URLConnection.openConnection() is invoked).
*
* <P>A second potential incompatibility is that the HTTPClient aggresively
* resuses connections, and can do so more often that Sun's client. This
* can cause problems if you send multiple requests, and the first one has
* a long response. In this case (assuming the server allows the connection
* to be used for multiple requests) the responses to second, third, etc
* request won't be received until the first response has been completely
* read. With Sun's client on the other hand you may not experience this,
* as it may not be able to keep the connection open and there may create
* multiple connections for the requests. This allows the responses to the
* second, third, etc requests to be read before the first response has
* completed. <strong>Note:</strong> whether this will happen depends on
* details of the resource being requested and the server. In many cases
* the HTTPClient and Sun's client will exhibit the same behaviour. Also,
* applications which depend on being able to read the second response
* before the first one has completed must be considered broken, because
* A) this behaviour cannot be relied upon even in Sun's current client,
* and B) Sun's implementation will exhibit the same problem if they ever
* switch to HTTP/1.1.
*
* @version 0.3-3 06/05/2001
* @author Ronald Tschal�r
* @since V0.3
*/
public class HttpURLConnection extends java.net.HttpURLConnection
{
/** the cache of HTTPConnections */
protected static Hashtable<String, HTTPConnection> connections = new Hashtable<>();
/** the current connection */
protected HTTPConnection con;
/** the cached url.toString() */
private String urlString;
/** the resource */
private String resource;
/** the current method */
private String method;
/** has the current method been set via setRequestMethod()? */
private boolean method_set;
/** the default request headers */
private static NVPair[] default_headers = new NVPair[0];
/** the request headers */
private NVPair[] headers;
/** the response */
protected HTTPResponse resp;
/** is the redirection module activated for this instance? */
private boolean do_redir;
/** the RedirectionModule class */
private static Class<?> redir_mod;
/** the output stream used for POST and PUT */
private OutputStream output_stream;
static
{
// The default allowUserAction in java.net.URLConnection is
// false.
try
{
if (Boolean.getBoolean("HTTPClient.HttpURLConnection.AllowUI"))
setDefaultAllowUserInteraction(true);
}
catch (SecurityException se)
{ }
// get the RedirectionModule class
try
{ redir_mod = Class.forName("HTTPClient.RedirectionModule"); }
catch (ClassNotFoundException cnfe)
{ throw new NoClassDefFoundError(cnfe.getMessage()); }
// Set the User-Agent if the http.agent property is set
try
{
String agent = System.getProperty("http.agent");
if (agent != null)
setDefaultRequestProperty("User-Agent", agent);
}
catch (SecurityException se)
{ }
}
// Constructors
private static String non_proxy_hosts = "";
private static String proxy_host = "";
private static int proxy_port = -1;
/**
* Construct a connection to the specified url. A cache of
* HTTPConnections is used to maximize the reuse of these across
* multiple HttpURLConnections.
*
* <BR>The default method is "GET".
*
* @param url the url of the request
* @exception ProtocolNotSuppException if the protocol is not supported
*/
public HttpURLConnection(URL url)
throws ProtocolNotSuppException, IOException
{
super(url);
// first read proxy properties and set
try
{
String hosts = System.getProperty("http.nonProxyHosts", "");
if (!hosts.equalsIgnoreCase(non_proxy_hosts))
{
connections.clear();
non_proxy_hosts = hosts;
String[] list = Util.splitProperty(hosts);
for (int idx=0; idx<list.length; idx++)
HTTPConnection.dontProxyFor(list[idx]);
}
}
catch (ParseException pe)
{ throw new IOException(pe.toString()); }
catch (SecurityException se)
{ }
try
{
String host = System.getProperty("http.proxyHost", "");
int port = Integer.getInteger("http.proxyPort", -1).intValue();
if (!host.equalsIgnoreCase(proxy_host) || port != proxy_port)
{
connections.clear();
proxy_host = host;
proxy_port = port;
HTTPConnection.setProxyServer(host, port);
}
}
catch (SecurityException se)
{ }
// now setup stuff
con = getConnection(url);
method = "GET";
method_set = false;
resource = url.getFile();
headers = default_headers;
do_redir = getFollowRedirects();
output_stream = null;
urlString = url.toString();
}
/**
* Returns an HTTPConnection. A cache of connections is kept and first
* consulted; only when the cache lookup fails is a new one created
* and added to the cache.
*
* @param url the url
* @return an HTTPConnection
* @exception ProtocolNotSuppException if the protocol is not supported
*/
protected HTTPConnection getConnection(URL url)
throws ProtocolNotSuppException
{
// try the cache, using the host name
String php = url.getProtocol() + ":" + url.getHost() + ":" +
((url.getPort() != -1) ? url.getPort() :
URI.defaultPort(url.getProtocol()));
php = php.toLowerCase();
HTTPConnection con = connections.get(php);
if (con != null) return con;
// Not in cache, so create new one and cache it
con = new HTTPConnection(url);
connections.put(php, con);
return con;
}
// Methods
/**
* Sets the request method (e.g. "PUT" or "HEAD"). Can only be set
* before connect() is called.
*
* @param method the http method.
* @exception ProtocolException if already connected.
*/
public void setRequestMethod(String method) throws ProtocolException
{
if (connected)
throw new ProtocolException("Already connected!");
Log.write(Log.URLC, "URLC: (" + urlString + ") Setting request method: " +
method);
this.method = method.trim().toUpperCase();
method_set = true;
}
/**
* Return the request method used.
*
* @return the http method.
*/
public String getRequestMethod()
{
return method;
}
/**
* Get the response code. Calls connect() if not connected.
*
* @return the http response code returned.
*/
public int getResponseCode() throws IOException
{
if (!connected) connect();
try
{ return resp.getStatusCode(); }
catch (ModuleException me)
{ throw new IOException(me.toString()); }
}
/**
* Get the response message describing the response code. Calls connect()
* if not connected.
*
* @return the http response message returned with the response code.
*/
public String getResponseMessage() throws IOException
{
if (!connected) connect();
try
{ return resp.getReasonLine(); }
catch (ModuleException me)
{ throw new IOException(me.toString()); }
}
/**
* Get the value part of a header. Calls connect() if not connected.
*
* @param name the of the header.
* @return the value of the header, or null if no such header was returned.
*/
public String getHeaderField(String name)
{
try
{
if (!connected) connect();
return resp.getHeader(name);
}
catch (Exception e)
{ return null; }
}
/**
* Get the value part of a header and converts it to an int. If the
* header does not exist or if its value could not be converted to an
* int then the default is returned. Calls connect() if not connected.
*
* @param name the of the header.
* @param def the default value to return in case of an error.
* @return the value of the header, or null if no such header was returned.
*/
public int getHeaderFieldInt(String name, int def)
{
try
{
if (!connected) connect();
return resp.getHeaderAsInt(name);
}
catch (Exception e)
{ return def; }
}
/**
* Get the value part of a header, interprets it as a date and converts
* it to a long representing the number of milliseconds since 1970. If
* the header does not exist or if its value could not be converted to a
* date then the default is returned. Calls connect() if not connected.
*
* @param name the of the header.
* @param def the default value to return in case of an error.
* @return the value of the header, or def in case of an error.
*/
public long getHeaderFieldDate(String name, long def)
{
try
{
if (!connected) connect();
return resp.getHeaderAsDate(name).getTime();
}
catch (Exception e)
{ return def; }
}
private String[] hdr_keys, hdr_values;
/**
* Gets header name of the n-th header. Calls connect() if not connected.
* The name of the 0-th header is <var>null</var>, even though it the
* 0-th header has a value.
*
* @param n which header to return.
* @return the header name, or null if not that many headers.
*/
public String getHeaderFieldKey(int n)
{
if (hdr_keys == null)
fill_hdr_arrays();
if (n >= 0 && n < hdr_keys.length)
return hdr_keys[n];
else
return null;
}
/**
* Gets header value of the n-th header. Calls connect() if not connected.
* The value of 0-th header is the Status-Line (e.g. "HTTP/1.1 200 Ok").
*
* @param n which header to return.
* @return the header value, or null if not that many headers.
*/
public String getHeaderField(int n)
{
if (hdr_values == null)
fill_hdr_arrays();
if (n >= 0 && n < hdr_values.length)
return hdr_values[n];
else
return null;
}
/**
* Cache the list of headers.
*/
private void fill_hdr_arrays()
{
try
{
if (!connected) connect();
// count number of headers
int num = 1;
Enumeration enumera = resp.listHeaders();
while (enumera.hasMoreElements())
{
num++;
enumera.nextElement();
}
// allocate arrays
hdr_keys = new String[num];
hdr_values = new String[num];
// fill arrays
enumera = resp.listHeaders();
for (int idx=1; idx<num; idx++)
{
hdr_keys[idx] = (String) enumera.nextElement();
hdr_values[idx] = resp.getHeader(hdr_keys[idx]);
}
// the 0'th field is special
hdr_values[0] = resp.getVersion() + " " + resp.getStatusCode() +
" " + resp.getReasonLine();
}
catch (Exception e)
{ hdr_keys = hdr_values = new String[0]; }
}
/**
* Gets an input stream from which the data in the response may be read.
* Calls connect() if not connected.
*
* @return an InputStream
* @exception ProtocolException if input not enabled.
* @see java.net.URLConnection#setDoInput(boolean)
*/
public InputStream getInputStream() throws IOException
{
if (!doInput)
throw new ProtocolException("Input not enabled! (use setDoInput(true))");
if (!connected) connect();
InputStream stream;
try
{ stream = resp.getInputStream(); }
catch (ModuleException e)
{ throw new IOException(e.toString()); }
return stream;
}
/**
* Returns the error stream if the connection failed
* but the server sent useful data nonetheless.
*
* <P>This method will not cause a connection to be initiated.
*
* @return an InputStream, or null if either the connection hasn't
* been established yet or no error occured
* @see java.net.HttpURLConnection#getErrorStream()
* @since V0.3-1
*/
public InputStream getErrorStream()
{
try
{
if (!doInput || !connected || resp.getStatusCode() < 300 ||
resp.getHeaderAsInt("Content-length") <= 0)
return null;
return resp.getInputStream();
}
catch (Exception e)
{ return null; }
}
/**
* Gets an output stream which can be used send an entity with the
* request. Can be called multiple times, in which case always the
* same stream is returned.
*
* <P>The default request method changes to "POST" when this method is
* called. Cannot be called after connect().
*
* <P>If no Content-type has been set it defaults to
* <var>application/x-www-form-urlencoded</var>. Furthermore, if the
* Content-type is <var>application/x-www-form-urlencoded</var> then all
* output will be collected in a buffer before sending it to the server;
* otherwise an HttpOutputStream is used.
*
* @return an OutputStream
* @exception ProtocolException if already connect()'ed, if output is not
* enabled or if the request method does not
* support output.
* @see java.net.URLConnection#setDoOutput(boolean)
* @see HTTPClient.HttpOutputStream
*/
public synchronized OutputStream getOutputStream() throws IOException
{
if (connected)
throw new ProtocolException("Already connected!");
if (!doOutput)
throw new ProtocolException("Output not enabled! (use setDoOutput(true))");
if (!method_set)
method = "POST";
else if (method.equals("HEAD") || method.equals("GET") ||
method.equals("TRACE"))
throw new ProtocolException("Method "+method+" does not support output!");
if (getRequestProperty("Content-type") == null)
setRequestProperty("Content-type", "application/x-www-form-urlencoded");
if (output_stream == null)
{
Log.write(Log.URLC, "URLC: (" + urlString + ") creating output stream");
String cl = getRequestProperty("Content-Length");
if (cl != null)
output_stream = new HttpOutputStream(Integer.parseInt(cl.trim()));
else
{
// Hack: because of restrictions when using true output streams
// and because form-data is usually quite limited in size, we
// first collect all data before sending it if this is
// form-data.
if (getRequestProperty("Content-type").equals(
"application/x-www-form-urlencoded"))
output_stream = new ByteArrayOutputStream(300);
else
output_stream = new HttpOutputStream();
}
if (output_stream instanceof HttpOutputStream)
connect();
}
return output_stream;
}
/**
* Gets the url for this connection. If we're connect()'d and the request
* was redirected then the url returned is that of the final request.
*
* @return the final url, or null if any exception occured.
*/
public URL getURL()
{
if (connected)
{
try
{ return resp.getEffectiveURI().toURL(); }
catch (Exception e)
{ return null; }
}
return url;
}
/**
* Sets the <var>If-Modified-Since</var> header.
*
* @param time the number of milliseconds since 1970.
*/
public void setIfModifiedSince(long time)
{
super.setIfModifiedSince(time);
setRequestProperty("If-Modified-Since", Util.httpDate(new Date(time)));
}
/**
* Sets an arbitrary request header.
*
* @param name the name of the header.
* @param value the value for the header.
*/
public void setRequestProperty(String name, String value)
{
Log.write(Log.URLC, "URLC: (" + urlString + ") Setting request property: " +
name + " : " + value);
int idx;
for (idx=0; idx<headers.length; idx++)
{
if (headers[idx].getName().equalsIgnoreCase(name))
break;
}
if (idx == headers.length)
headers = Util.resizeArray(headers, idx+1);
headers[idx] = new NVPair(name, value);
}
/**
* Gets the value of a given request header.
*
* @param name the name of the header.
* @return the value part of the header, or null if no such header.
*/
public String getRequestProperty(String name)
{
for (int idx=0; idx<headers.length; idx++)
{
if (headers[idx].getName().equalsIgnoreCase(name))
return headers[idx].getValue();
}
return null;
}
/**
* Sets an arbitrary default request header. All headers set here are
* automatically sent with each request.
*
* @param name the name of the header.
* @param value the value for the header.
*/
public static void setDefaultRequestProperty(String name, String value)
{
Log.write(Log.URLC, "URLC: Setting default request property: " +
name + " : " + value);
int idx;
for (idx=0; idx<default_headers.length; idx++)
{
if (default_headers[idx].getName().equalsIgnoreCase(name))
break;
}
if (idx == default_headers.length)
default_headers = Util.resizeArray(default_headers, idx+1);
default_headers[idx] = new NVPair(name, value);
}
/**
* Gets the value for a given default request header.
*
* @param name the name of the header.
* @return the value part of the header, or null if no such header.
*/
public static String getDefaultRequestProperty(String name)
{
for (int idx=0; idx<default_headers.length; idx++)
{
if (default_headers[idx].getName().equalsIgnoreCase(name))
return default_headers[idx].getValue();
}
return null;
}
/**
* Enables or disables the automatic handling of redirection responses
* for this instance only. Cannot be called after <code>connect()</code>.
*
* @param set enables automatic redirection handling if true.
*/
public void setInstanceFollowRedirects(boolean set)
{
if (connected)
throw new IllegalStateException("Already connected!");
do_redir = set;
}
/**
* @return true if automatic redirection handling for this instance is
* enabled.
*/
public boolean getInstanceFollowRedirects()
{
return do_redir;
}
/**
* Connects to the server (if connection not still kept alive) and
* issues the request.
*/
public synchronized void connect() throws IOException
{
if (connected) return;
Log.write(Log.URLC, "URLC: (" + urlString + ") Connecting ...");
// useCaches TBD!!!
synchronized (con)
{
con.setAllowUserInteraction(allowUserInteraction);
if (do_redir)
con.addModule(redir_mod, 2);
else
con.removeModule(redir_mod);
try
{
if (output_stream instanceof ByteArrayOutputStream)
resp = con.ExtensionMethod(method, resource,
((ByteArrayOutputStream) output_stream).toByteArray(),
headers);
else
resp = con.ExtensionMethod(method, resource,
(HttpOutputStream) output_stream, headers);
}
catch (ModuleException e)
{ throw new IOException(e.toString()); }
}
connected = true;
}
/**
* Closes all the connections to this server.
*/
public void disconnect()
{
Log.write(Log.URLC, "URLC: (" + urlString + ") Disconnecting ...");
con.stop();
}
/**
* Shows if request are being made through an http proxy or directly.
*
* @return true if an http proxy is being used.
*/
public boolean usingProxy()
{
return (con.getProxyHost() != null);
}
/**
* produces a string.
* @return a string containing the HttpURLConnection
*/
public String toString()
{
return getClass().getName() + "[" + url + "]";
}
}