Package HTTPClient

Source Code of HTTPClient.HttpURLConnection

/*
* @(#)HttpURLConnection.java        0.3-3 18/06/1999
*
*  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.io.IOException;
import java.io.InterruptedIOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.net.URL;
import java.net.ProtocolException;
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 is a hacked version of the HttpURLConnection class for use
* with HotJava 1.1.x. It extends sun.net.www.protocol.http.HttpURLConnection
* instead of java.net.HttpURLConnection, because the HotJava uses the
* setInstanceFollowRedirect() method which is defined in the
* sun.*.HttpURLConnection class, but only if this is an instance of the
* sun.*.HttpURLConnection class (or a subclass).
*
* <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  18/06/1999
* @author  Ronald Tschal�r
* @since  V0.3
*/

public class HttpURLConnection
    extends sun.net.www.protocol.http.HttpURLConnection
    implements GlobalConstants
{
    /** a list of HTTPConnections */
    private static Hashtable  connections = new Hashtable();

    /** the current connection */
    private 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 */
    private 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;

    /** HotJava hacks */
    private static boolean    in_hotjava = false;


    static
    {
  try
  {
      String browser = System.getProperty("browser");
      if (browser != null  &&  browser.equals("HotJava"))
    in_hotjava = true;
  }
  catch (SecurityException se)
      { }

  // The default allowUserAction in java.net.URLConnection is
  // false.
  try
  {
      if (Boolean.getBoolean("HTTPClient.HttpURLConnection.AllowUI"||
    in_hotjava)
    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, null);

  // 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
     */
    private 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 = (HTTPConnection) connections.get(php);
  if (con != nullreturn 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 methodthrows 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 (!connectedconnect();

  try
  {
      if (in_hotjava  &&  resp.getStatusCode() >= 300)
      {
    try
        { resp.getData(); // force response stream to be read
    catch (InterruptedIOException iioe)
        { disconnect(); }
      }
      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 (!connectedconnect();

  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 (!connectedconnect();
      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 (!connectedconnect();
      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 (!connectedconnect();
      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 >= &&  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 >= &&  n < hdr_values.length)
      return hdr_values[n];
  else
      return null;
    }


    /**
     * Cache the list of headers.
     */
    private void fill_hdr_arrays()
    {
  try
  {
      if (!connectedconnect();

      // 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; idx<num; idx++)
      {
    hdr_keys[idx]   = (String) enum.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 (!connectedconnect();

  InputStream stream;
  try
  {
      stream = resp.getInputStream();

      if (resp.pe != null  &&  resp.getHeader("Content-length") != null)
      {
    try
        { stream = new sun.net.www.MeteredStream(stream, resp.pe); }
    catch (Throwable t)
    {
        Log.write(Log.URLC, "URLC:  (" + urlString +
          ") error trying to create metered stream: ", t);
    }
      }
      else
    // some things expect this stream to support mark/reset
    stream = new BufferedInputStream(stream);
  }
  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
      {
    if (resp.getEffectiveURL() != null)
        return resp.getEffectiveURL();
      }
      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 (connectedreturn;

  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;

  try
      { resp.setProgressEntry(new sun.net.ProgressEntry(url.getFile(), null)); }
  catch (Throwable t)
  {
      Log.write(Log.URLC, "URLC:  (" + urlString +
        ") error trying to create and set progress entry: ", t);
  }
    }


    /**
     * Closes all the connections to this server.
     */
    public void disconnect()
    {
  Log.write(Log.URLC, "URLC:  (" + urlString + ") Disconnecting ...");

  con.stop();

  if (resp != null)
      resp.unsetProgressEntry();
    }


    public void finalize()
    {
  if (resp != null)
      resp.unsetProgressEntry();

  super.finalize();
    }


    /**
     * 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 + "]";
    }
}
TOP

Related Classes of HTTPClient.HttpURLConnection

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.