/* * @(#)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. * *

This class can be used to replace the HttpClient in the JDK with this * HTTPClient by defining the property * java.protocol.handler.pkgs=HTTPClient. * *

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: *

 *   HttpURLConnection con = (HttpURLConnection) url.openConnection();
 *
 *   con.setDoOutput(true);
 *   con.setRequestProperty("Content-Length", ...);
 *   OutputStream out = con.getOutputStream();
 *
 *   out.write(...);
 *   out.close();
 *
 *   if (con.getResponseCode() != 200)
 *       ...
 * 
* *

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 * HTTPClient.dontChunkRequests to true (this needs * to be done either on the command line or somewhere in the code before * the first URLConnection.openConnection() is invoked). * *

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. Note: 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 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. * *
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; idxnull, 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 enum = resp.listHeaders(); while (enum.hasMoreElements()) { num++; enum.nextElement(); } // allocate arrays hdr_keys = new String[num]; hdr_values = new String[num]; // fill arrays enum = resp.listHeaders(); for (int idx=1; idxThis 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. * *

The default request method changes to "POST" when this method is * called. Cannot be called after connect(). * *

If no Content-type has been set it defaults to * application/x-www-form-urlencoded. Furthermore, if the * Content-type is application/x-www-form-urlencoded 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 If-Modified-Since 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; idxconnect(). * * @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 + "]"; } }