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