KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > HTTPClient > HttpURLConnection


1 /*
2  * @(#)HttpURLConnection.java 0.3-2 18/06/1999
3  *
4  * This file is part of the HTTPClient package
5  * Copyright (C) 1996-1999 Ronald Tschalär
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public
9  * License as published by the Free Software Foundation; either
10  * version 2 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with this library; if not, write to the Free
19  * Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
20  * MA 02111-1307, USA
21  *
22  * For questions, suggestions, bug-reports, enhancement-requests etc.
23  * I may be contacted at:
24  *
25  * ronald@innovation.ch
26  *
27  */

28
29 package HTTPClient;
30
31 import java.net.URL;
32 import java.net.ProtocolException;
33 import java.net.UnknownHostException;
34 import java.io.IOException;
35 import java.io.InterruptedIOException;
36 import java.io.InputStream;
37 import java.io.OutputStream;
38 import java.io.BufferedInputStream;
39 import java.io.ByteArrayOutputStream;
40 import java.util.Date;
41 import java.util.Hashtable;
42 import java.util.Enumeration;
43
44
45 /**
46  * This class is a wrapper around HTTPConnection providing the interface
47  * defined by java.net.URLConnection and java.net.HttpURLConnection.
48  *
49  * <P>This class can be used to replace the HttpClient in the JDK with this
50  * HTTPClient by defining the property
51  * <code>java.protocol.handler.pkgs=HTTPClient</code>.
52  *
53  * <P>One difference between Sun's HttpClient and this one is that this
54  * one will provide you with a real output stream if possible. This leads
55  * to two changes: you should set the request property "Content-Length",
56  * if possible, before invoking getOutputStream(); and in many cases
57  * getOutputStream() implies connect(). This should be transparent, though,
58  * apart from the fact that you can't change any headers or other settings
59  * anymore once you've gotten the output stream.
60  * So, for large data do:
61  * <PRE>
62  * HttpURLConnection con = (HttpURLConnection) url.openConnection();
63  *
64  * con.setDoOutput(true);
65  * con.setRequestProperty("Content-Length", ...);
66  * OutputStream out = con.getOutputStream();
67  *
68  * out.write(...);
69  * out.close();
70  *
71  * if (con.getResponseCode() != 200)
72  * ...
73  * </PRE>
74  *
75  * <P>The HTTPClient will send the request data using the chunked transfer
76  * encoding when no Content-Length is specified and the server is HTTP/1.1
77  * compatible. Because cgi-scripts can't usually handle this, you may
78  * experience problems trying to POST data. For this reason, whenever
79  * the Content-Type is application/x-www-form-urlencoded getOutputStream()
80  * will buffer the data before sending it so as prevent chunking. If you
81  * are sending requests with a different Content-Type and are experiencing
82  * problems then you may want to try setting the system property
83  * <var>HTTPClient.dontChunkRequests</var> to <var>true</var> (this needs
84  * to be done either on the command line or somewhere in the code before
85  * the first URLConnection.openConnection() is invoked).
86  *
87  * @version 0.3-2 18/06/1999
88  * @author Ronald Tschalär
89  * @since V0.3
90  */

91
92 public class HttpURLConnection extends java.net.HttpURLConnection
93                    implements GlobalConstants
94 {
95     /** a list of HTTPConnections */
96     private static Hashtable connections = new Hashtable();
97
98     /** the current connection */
99     private HTTPConnection con;
100
101     /** the resource */
102     private String resource;
103
104     /** the current method */
105     private String method;
106
107     /** has the current method been set via setRequestMethod()? */
108     private boolean method_set;
109
110     /** the default request headers */
111     private static NVPair[] default_headers = new NVPair[0];
112
113     /** the request headers */
114     private NVPair[] headers;
115
116     /** the response */
117     private HTTPResponse resp;
118
119     /** is the redirection module activated for this instance? */
120     private boolean do_redir;
121
122     /** the RedirectionModule class */
123     private static Class redir_mod;
124
125     /** the output stream used for POST and PUT */
126     private OutputStream output_stream;
127
128
129     static
130     {
131     // The default allowUserAction in java.net.URLConnection is
132
// false.
133
try
134     {
135         if (Boolean.getBoolean("HTTPClient.HttpURLConnection.AllowUI"))
136         setDefaultAllowUserInteraction(true);
137     }
138     catch (SecurityException se)
139         { }
140
141     // get the RedirectionModule class
142
try
143         { redir_mod = Class.forName("HTTPClient.RedirectionModule"); }
144     catch (ClassNotFoundException cnfe)
145         { throw new NoClassDefFoundError(cnfe.getMessage()); }
146
147     // Set the User-Agent if the http.agent property is set
148
try
149     {
150         String agent = System.getProperty("http.agent");
151         if (agent != null)
152         setDefaultRequestProperty("User-Agent", agent);
153     }
154     catch (SecurityException se)
155         { }
156     }
157
158
159     // Constructors
160

161     private static String non_proxy_hosts = "";
162     private static String proxy_host = "";
163     private static int proxy_port = -1;
164
165     /**
166      * Construct a connection to the specified url. A cache of
167      * HTTPConnections is used to maximize the reuse of these across
168      * multiple HttpURLConnections.
169      *
170      * <BR>The default method is "GET".
171      *
172      * @param url the url of the request
173      * @exception ProtocolNotSuppException if the protocol is not supported
174      */

175     public HttpURLConnection(URL url)
176         throws ProtocolNotSuppException, IOException
177     {
178     super(url);
179
180     // first read proxy properties and set
181
try
182         {
183             String hosts = System.getProperty("http.nonProxyHosts", "");
184         if (!hosts.equalsIgnoreCase(non_proxy_hosts))
185         {
186         connections.clear();
187         non_proxy_hosts = hosts;
188         String[] list = Util.splitProperty(hosts);
189         for (int idx=0; idx<list.length; idx++)
190             HTTPConnection.dontProxyFor(list[idx]);
191         }
192         }
193         catch (ParseException pe)
194         { throw new IOException(pe.toString()); }
195         catch (SecurityException se)
196             { }
197
198     try
199     {
200         String host = System.getProperty("http.proxyHost", "");
201         int port = Integer.getInteger("http.proxyPort", -1).intValue();
202         if (!host.equalsIgnoreCase(proxy_host) || port != proxy_port)
203         {
204         connections.clear();
205         proxy_host = host;
206         proxy_port = port;
207         HTTPConnection.setProxyServer(host, port);
208         }
209     }
210     catch (SecurityException se)
211         { }
212
213     // now setup stuff
214
con = getConnection(url);
215     method = "GET";
216     method_set = false;
217     resource = url.getFile();
218     headers = default_headers;
219     do_redir = getFollowRedirects();
220     output_stream = null;
221     }
222
223
224     /**
225      * Returns an HTTPConnection. A cache of connections is kept and first
226      * consulted; only when the cache lookup fails is a new one created
227      * and added to the cache.
228      *
229      * @param url the url
230      * @return an HTTPConnection
231      * @exception ProtocolNotSuppException if the protocol is not supported
232      */

233     private HTTPConnection getConnection(URL url)
234         throws ProtocolNotSuppException
235     {
236     // try the cache, using the host name
237

238     String php = url.getProtocol() + ":" + url.getHost() + ":" +
239              ((url.getPort() != -1) ? url.getPort() :
240                     URI.defaultPort(url.getProtocol()));
241     php = php.toLowerCase();
242
243     HTTPConnection con = (HTTPConnection) connections.get(php);
244     if (con != null) return con;
245
246
247     // Not in cache, so create new one and cache it
248

249     con = new HTTPConnection(url);
250     connections.put(php, con);
251
252     return con;
253     }
254
255
256     // Methods
257

258     /**
259      * Sets the request method (e.g. "PUT" or "HEAD"). Can only be set
260      * before connect() is called.
261      *
262      * @param method the http method.
263      * @exception ProtocolException if already connected.
264      */

265     public void setRequestMethod(String method) throws ProtocolException
266     {
267     if (connected)
268         throw new ProtocolException("Already connected!");
269
270     if (DebugURLC)
271         System.err.println("URLC: (" + url + ") Setting request method: " +
272                    method);
273
274     this.method = method.trim().toUpperCase();
275     method_set = true;
276     }
277
278
279     /**
280      * Return the request method used.
281      *
282      * @return the http method.
283      */

284     public String getRequestMethod()
285     {
286     return method;
287     }
288
289
290     /**
291      * Get the response code. Calls connect() if not connected.
292      *
293      * @return the http response code returned.
294      */

295     public int getResponseCode() throws IOException
296     {
297     if (!connected) connect();
298
299     try
300         { return resp.getStatusCode(); }
301     catch (ModuleException me)
302         { throw new IOException(me.toString()); }
303     }
304
305
306     /**
307      * Get the response message describing the response code. Calls connect()
308      * if not connected.
309      *
310      * @return the http response message returned with the response code.
311      */

312     public String getResponseMessage() throws IOException
313     {
314     if (!connected) connect();
315
316     try
317         { return resp.getReasonLine(); }
318     catch (ModuleException me)
319         { throw new IOException(me.toString()); }
320     }
321
322
323     /**
324      * Get the value part of a header. Calls connect() if not connected.
325      *
326      * @param name the of the header.
327      * @return the value of the header, or null if no such header was returned.
328      */

329     public String getHeaderField(String name)
330     {
331     try
332     {
333         if (!connected) connect();
334         return resp.getHeader(name);
335     }
336     catch (Exception e)
337         { return null; }
338     }
339
340
341     /**
342      * Get the value part of a header and converts it to an int. If the
343      * header does not exist or if its value could not be converted to an
344      * int then the default is returned. Calls connect() if not connected.
345      *
346      * @param name the of the header.
347      * @param def the default value to return in case of an error.
348      * @return the value of the header, or null if no such header was returned.
349      */

350     public int getHeaderFieldInt(String name, int def)
351     {
352     try
353     {
354         if (!connected) connect();
355         return resp.getHeaderAsInt(name);
356     }
357     catch (Exception e)
358         { return def; }
359     }
360
361
362     /**
363      * Get the value part of a header, interprets it as a date and converts
364      * it to a long representing the number of milliseconds since 1970. If
365      * the header does not exist or if its value could not be converted to a
366      * date then the default is returned. Calls connect() if not connected.
367      *
368      * @param name the of the header.
369      * @param def the default value to return in case of an error.
370      * @return the value of the header, or def in case of an error.
371      */

372     public long getHeaderFieldDate(String name, long def)
373     {
374     try
375     {
376         if (!connected) connect();
377         return resp.getHeaderAsDate(name).getTime();
378     }
379     catch (Exception e)
380         { return def; }
381     }
382
383
384     private String[] hdr_keys, hdr_values;
385
386     /**
387      * Gets header name of the n-th header. Calls connect() if not connected.
388      * The name of the 0-th header is <var>null</var>, even though it the
389      * 0-th header has a value.
390      *
391      * @param n which header to return.
392      * @return the header name, or null if not that many headers.
393      */

394     public String getHeaderFieldKey(int n)
395     {
396     if (hdr_keys == null)
397         fill_hdr_arrays();
398
399     if (n >= 0 && n < hdr_keys.length)
400         return hdr_keys[n];
401     else
402         return null;
403     }
404
405
406     /**
407      * Gets header value of the n-th header. Calls connect() if not connected.
408      * The value of 0-th header is the Status-Line (e.g. "HTTP/1.1 200 Ok").
409      *
410      * @param n which header to return.
411      * @return the header value, or null if not that many headers.
412      */

413     public String getHeaderField(int n)
414     {
415     if (hdr_values == null)
416         fill_hdr_arrays();
417
418     if (n >= 0 && n < hdr_values.length)
419         return hdr_values[n];
420     else
421         return null;
422     }
423
424
425     /**
426      * Cache the list of headers.
427      */

428     private void fill_hdr_arrays()
429     {
430     try
431     {
432         if (!connected) connect();
433
434         // count number of headers
435
int num = 1;
436         Enumeration enum = resp.listHeaders();
437         while (enum.hasMoreElements())
438         {
439         num++;
440         enum.nextElement();
441         }
442
443         // allocate arrays
444
hdr_keys = new String[num];
445         hdr_values = new String[num];
446
447         // fill arrays
448
enum = resp.listHeaders();
449         for (int idx=1; idx<num; idx++)
450         {
451         hdr_keys[idx] = (String) enum.nextElement();
452         hdr_values[idx] = resp.getHeader(hdr_keys[idx]);
453         }
454
455         // the 0'th field is special
456
hdr_values[0] = resp.getVersion() + " " + resp.getStatusCode() +
457                 " " + resp.getReasonLine();
458     }
459     catch (Exception e)
460         { hdr_keys = hdr_values = new String[0]; }
461     }
462
463
464     /**
465      * Gets an input stream from which the data in the response may be read.
466      * Calls connect() if not connected.
467      *
468      * @return an InputStream
469      * @exception ProtocolException if input not enabled.
470      * @see java.net.URLConnection#setDoInput(boolean)
471      */

472     public InputStream getInputStream() throws IOException
473     {
474     if (!doInput)
475         throw new ProtocolException("Input not enabled! (use setDoInput(true))");
476
477     if (!connected) connect();
478
479     InputStream stream;
480     try
481         { stream = resp.getInputStream(); }
482     catch (ModuleException e)
483         { throw new IOException(e.toString()); }
484
485     return stream;
486     }
487
488
489     /**
490      * Returns the error stream if the connection failed
491      * but the server sent useful data nonetheless.
492      *
493      * <P>This method will not cause a connection to be initiated.
494      *
495      * @return an InputStream, or null if either the connection hasn't
496      * been established yet or no error occured
497      * @see java.net.HttpURLConnection#getErrorStream()
498      * @since V0.3-1
499      */

500     public InputStream getErrorStream()
501     {
502     try
503     {
504         if (!doInput || !connected || resp.getStatusCode() < 300 ||
505         resp.getHeaderAsInt("Content-length") <= 0)
506         return null;
507
508         return resp.getInputStream();
509     }
510     catch (Exception e)
511         { return null; }
512     }
513
514
515     /**
516      * Gets an output stream which can be used send an entity with the
517      * request. Can be called multiple times, in which case always the
518      * same stream is returned.
519      *
520      * <P>The default request method changes to "POST" when this method is
521      * called. Cannot be called after connect().
522      *
523      * <P>If no Content-type has been set it defaults to
524      * <var>application/x-www-form-urlencoded</var>. Furthermore, if the
525      * Content-type is <var>application/x-www-form-urlencoded</var> then all
526      * output will be collected in a buffer before sending it to the server;
527      * otherwise an HttpOutputStream is used.
528      *
529      * @return an OutputStream
530      * @exception ProtocolException if already connect()'ed, if output is not
531      * enabled or if the request method does not
532      * support output.
533      * @see java.net.URLConnection#setDoOutput(boolean)
534      * @see HTTPClient.HttpOutputStream
535      */

536     public synchronized OutputStream getOutputStream() throws IOException
537     {
538     if (connected)
539         throw new ProtocolException("Already connected!");
540
541     if (!doOutput)
542         throw new ProtocolException("Output not enabled! (use setDoOutput(true))");
543     if (!method_set)
544         method = "POST";
545     else if (method.equals("HEAD") || method.equals("GET") ||
546          method.equals("TRACE"))
547         throw new ProtocolException("Method "+method+" does not support output!");
548
549     if (getRequestProperty("Content-type") == null)
550         setRequestProperty("Content-type", "application/x-www-form-urlencoded");
551
552     if (output_stream == null)
553     {
554         if (DebugURLC)
555         System.err.println("URLC: (" +url+ ") creating output stream");
556
557         String cl = getRequestProperty("Content-Length");
558         if (cl != null)
559         output_stream = new HttpOutputStream(Integer.parseInt(cl));
560         else
561         {
562         // Hack: because of restrictions when using true output streams
563
// and because form-data is usually quite limited in size, we
564
// first collect all data before sending it if this is
565
// form-data.
566
if (getRequestProperty("Content-type").equals(
567             "application/x-www-form-urlencoded"))
568             output_stream = new ByteArrayOutputStream(300);
569         else
570             output_stream = new HttpOutputStream();
571         }
572
573         if (output_stream instanceof HttpOutputStream)
574         connect();
575     }
576
577     return output_stream;
578     }
579
580
581     /**
582      * Gets the url for this connection. If we're connect()'d and the request
583      * was redirected then the url returned is that of the final request.
584      *
585      * @return the final url, or null if any exception occured.
586      */

587     public URL getURL()
588     {
589     if (connected)
590     {
591         try
592         {
593         if (resp.getEffectiveURL() != null)
594             return resp.getEffectiveURL();
595         }
596         catch (Exception e)
597         { return null; }
598     }
599
600     return url;
601     }
602
603
604     /**
605      * Sets the <var>If-Modified-Since</var> header.
606      *
607      * @param time the number of milliseconds since 1970.
608      */

609     public void setIfModifiedSince(long time)
610     {
611     super.setIfModifiedSince(time);
612     setRequestProperty("If-Modified-Since", Util.httpDate(new Date(time)));
613     }
614
615
616     /**
617      * Sets an arbitrary request header.
618      *
619      * @param name the name of the header.
620      * @param value the value for the header.
621      */

622     public void setRequestProperty(String name, String value)
623     {
624     if (DebugURLC)
625         System.err.println("URLC: (" +url+ ") Setting request property: " +
626                    name + " : " + value);
627
628     int idx;
629     for (idx=0; idx<headers.length; idx++)
630     {
631         if (headers[idx].getName().equalsIgnoreCase(name))
632         break;
633     }
634
635     if (idx == headers.length)
636         headers = Util.resizeArray(headers, idx+1);
637
638     headers[idx] = new NVPair(name, value);
639     }
640
641
642     /**
643      * Gets the value of a given request header.
644      *
645      * @param name the name of the header.
646      * @return the value part of the header, or null if no such header.
647      */

648     public String getRequestProperty(String name)
649     {
650     for (int idx=0; idx<headers.length; idx++)
651     {
652         if (headers[idx].getName().equalsIgnoreCase(name))
653         return headers[idx].getValue();
654     }
655
656     return null;
657     }
658
659
660     /**
661      * Sets an arbitrary default request header. All headers set here are
662      * automatically sent with each request.
663      *
664      * @param name the name of the header.
665      * @param value the value for the header.
666      */

667     public static void setDefaultRequestProperty(String name, String value)
668     {
669     if (DebugURLC)
670         System.err.println("URLC: Setting default request property: " +
671                    name + " : " + value);
672
673     int idx;
674     for (idx=0; idx<default_headers.length; idx++)
675     {
676         if (default_headers[idx].getName().equalsIgnoreCase(name))
677         break;
678     }
679
680     if (idx == default_headers.length)
681         default_headers = Util.resizeArray(default_headers, idx+1);
682
683     default_headers[idx] = new NVPair(name, value);
684     }
685
686
687     /**
688      * Gets the value for a given default request header.
689      *
690      * @param name the name of the header.
691      * @return the value part of the header, or null if no such header.
692      */

693     public static String getDefaultRequestProperty(String name)
694     {
695     for (int idx=0; idx<default_headers.length; idx++)
696     {
697         if (default_headers[idx].getName().equalsIgnoreCase(name))
698         return default_headers[idx].getValue();
699     }
700
701     return null;
702     }
703
704
705     /**
706      * Enables or disables the automatic handling of redirection responses
707      * for this instance only. Cannot be called after <code>connect()</code>.
708      *
709      * @param set enables automatic redirection handling if true.
710      */

711     public void setInstanceFollowRedirects(boolean set)
712     {
713     if (connected)
714         throw new IllegalStateException("Already connected!");
715
716     do_redir = set;
717     }
718
719
720     /**
721      * @return true if automatic redirection handling for this instance is
722      * enabled.
723      */

724     public boolean getInstanceFollowRedirects()
725     {
726     return do_redir;
727     }
728
729
730     /**
731      * Connects to the server (if connection not still kept alive) and
732      * issues the request.
733      */

734     public synchronized void connect() throws IOException
735     {
736     if (connected) return;
737
738     if (DebugURLC)
739         System.err.println("URLC: (" + url + ") Connecting ...");
740
741     // useCaches TBD!!!
742

743     synchronized(con)
744     {
745         con.setAllowUserInteraction(allowUserInteraction);
746         if (do_redir)
747         con.addModule(redir_mod, 2);
748         else
749         con.removeModule(redir_mod);
750
751         try
752         {
753         if (output_stream instanceof ByteArrayOutputStream)
754             resp = con.ExtensionMethod(method, resource,
755             ((ByteArrayOutputStream) output_stream).toByteArray(),
756                          headers);
757         else
758             resp = con.ExtensionMethod(method, resource,
759                     (HttpOutputStream) output_stream, headers);
760         }
761         catch (ModuleException e)
762         { throw new IOException(e.toString()); }
763     }
764
765     connected = true;
766     }
767
768
769     /**
770      * Closes all the connections to this server.
771      */

772     public void disconnect()
773     {
774     if (DebugURLC)
775         System.err.println("URLC: (" + url + ") Disconnecting ...");
776
777     con.stop();
778     }
779
780
781     /**
782      * Shows if request are being made through an http proxy or directly.
783      *
784      * @return true if an http proxy is being used.
785      */

786     public boolean usingProxy()
787     {
788     return (con.getProxyHost() != null);
789     }
790
791
792     /**
793      * produces a string.
794      * @return a string containing the HttpURLConnection
795      */

796     public String toString()
797     {
798     return getClass().getName() + "[" + url + "]";
799     }
800 }
801
802
Popular Tags