commons-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From d...@apache.org
Subject cvs commit: jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/methods GetMethod.java PostMethod.java PutMethod.java
Date Tue, 23 Jul 2002 14:38:31 GMT
dion        2002/07/23 07:38:31

  Modified:    httpclient/src/java/org/apache/commons/httpclient
                        HttpClient.java HttpConnection.java
                        HttpConnectionManager.java HttpMethodBase.java
                        HttpMultiClient.java
               httpclient/src/java/org/apache/commons/httpclient/methods
                        GetMethod.java PostMethod.java PutMethod.java
  Added:       httpclient/src/java/org/apache/commons/httpclient
                        ConnectMethod.java
  Log:
  Proxy Authorisation patches as submitted by Ortwin Gluck
  
  Revision  Changes    Path
  1.52      +34 -7     jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/HttpClient.java
  
  Index: HttpClient.java
  ===================================================================
  RCS file: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/HttpClient.java,v
  retrieving revision 1.51
  retrieving revision 1.52
  diff -u -r1.51 -r1.52
  --- HttpClient.java	21 Jul 2002 05:20:13 -0000	1.51
  +++ HttpClient.java	23 Jul 2002 14:38:31 -0000	1.52
  @@ -67,6 +67,7 @@
   
   import java.io.IOException;
   import java.net.URL;
  +import javax.net.ssl.SSLSocketFactory;
   
   
   /**
  @@ -79,8 +80,10 @@
    * @author <a href="mailto:rwaldhoff@apache.org">Rodney Waldhoff</a>
    * @author Sean C. Sullivan
    * @author <a href="mailto:dion@apache.org">dIon Gillard</a>
  + * @author Ortwin Glück
    * @version $Revision$ $Date$
    */
  +
   public class HttpClient {
   
   
  @@ -109,6 +112,10 @@
        */
       private HttpState state;
   
  +    private SSLSocketFactory sslSocketFactory = null;
  +
  +    private int timeout = 0;
  +
       // ------------------------------------------------------------- Properties
   
       /**
  @@ -134,6 +141,23 @@
           this.state = state;
       }
   
  +    /**
  +     * Specifies an alternative factory for SSL sockets.
  +     * @see HttpConnection#setSSLSocketFactory(SSLSocketFactory) HttpConnection.setSSLSocketFactory
  +     * @param sslSocketFactory a living instance of the alternative SSLSocketFactory
  +     */
  +    public void setSSLSocketFactory(SSLSocketFactory sslSocketFactory) {
  +        this.sslSocketFactory = sslSocketFactory;
  +    }
  +
  +    /**
  +     * Sets the SO_TIMEOUT which is the timeout for waiting for data.
  +     * @param timeout Timeout in milliseconds
  +     */
  +    public void setTimeout(int timeout) {
  +        this.timeout = timeout;
  +    }
  +
       // --------------------------------------------------------- Public Methods
   
       /**
  @@ -313,7 +337,12 @@
           }
   
           if (!connection.isOpen()) {
  +            connection.setSSLSocketFactory(sslSocketFactory);
  +            connection.setSoTimeout(timeout);
               connection.open();
  +            if (connection.isProxied() && connection.isSecure()) {
  +                method = new ConnectMethod(method);
  +            }
           }
           return method.execute(getState(), connection);
       }
  @@ -332,9 +361,7 @@
        * @throws IOException when i/o errors occur closing the connection
        */
       public void endSession() throws IOException {
  -        if (log.isDebugEnabled()) {
  -            log.debug("HttpClient.endSession()");
  -        }
  +        log.debug("HttpClient.endSession()");
           if (null != connection) {
               connection.close();
               connection = null;
  
  
  
  1.12      +90 -9     jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/HttpConnection.java
  
  Index: HttpConnection.java
  ===================================================================
  RCS file: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/HttpConnection.java,v
  retrieving revision 1.11
  retrieving revision 1.12
  diff -u -r1.11 -r1.12
  --- HttpConnection.java	21 Jul 2002 04:12:44 -0000	1.11
  +++ HttpConnection.java	23 Jul 2002 14:38:31 -0000	1.12
  @@ -72,6 +72,8 @@
   import java.lang.reflect.Method;
   import java.net.Socket;
   import java.net.SocketException;
  +import javax.net.ssl.SSLSocketFactory;
  +import javax.net.SocketFactory;
   
   
   
  @@ -82,6 +84,7 @@
    * </p>
    * @author Rod Waldhoff
    * @author Sean C. Sullivan
  + * @author Ortwin Glück
    * @version $Revision$ $Date$
    */
   public class HttpConnection {
  @@ -131,7 +134,12 @@
        */
       public HttpConnection(String proxyHost, int proxyPort, String host, 
       int port, boolean secure) {
  -        log.debug("HttpConnection.HttpConnection");
  +        if  (log.isDebugEnabled()){
  +            log.debug("HttpConnectionManager.getConnection:  creating "
  +                + " connection for " + host + ":" + port + " via " + proxyHost
  +                + ":" + proxyPort);
  +        }
  +
           if (host == null) {
               throw new NullPointerException("host parameter is null");
           }
  @@ -145,6 +153,18 @@
       // ------------------------------------------ Attribute Setters and Getters
   
       /**
  +     * Specifies an alternative factory for SSL sockets. If <code>factory</code>
  +     * is <code>null</code> the default implementation is used.
  +     *
  +     * @param factory An instance of a SSLSocketFactory or <code>null</code>.
  +     * @throws IllegalStateException If called after the connection was opened
  +     */
  +    public void setSSLSocketFactory(SSLSocketFactory factory) {
  +        assertNotOpen();
  +        sslSocketFactory = factory;
  +    }
  +
  +    /**
        * Return my host.
        *
        * @return my host.
  @@ -284,7 +304,7 @@
        */
       public void setSoTimeout(int timeout) throws SocketException, 
       IllegalStateException {
  -        log.debug("HttpConnection.setSoTimeout()");
  +        log.debug("HttpConnection.setSoTimeout("+ timeout +")");
           _so_timeout = timeout;
           if(_socket != null){
               _socket.setSoTimeout(timeout);
  @@ -304,10 +324,15 @@
               if (null == _socket) {
                   String host = (null == _proxyHost) ? _host : _proxyHost;
                   int port = (null == _proxyHost) ? _port : _proxyPort;
  -                if (_ssl) {
  -                    _socket = SSLSocketFactory.getDefault().createSocket(host,port);
  +                if (isSecure() && !isProxied()) {
  +                    if (sslSocketFactory == null) {
  +                        sslSocketFactory = SSLSocketFactory.getDefault();
  +                    }
  +                    _socket = sslSocketFactory.createSocket(host,port);
  +                    _usingSecureSocket = true;
                   } else {
                       _socket = new Socket(host,port);
  +                    _usingSecureSocket = false;
                   }
               }
               _socket.setSoTimeout(_so_timeout);
  @@ -323,6 +348,42 @@
       }
   
       /**
  +     * Calling this method indicates that the proxy has successfully created
  +     * the tunnel to the host. The socket will be switched to the secure socket.
  +     * Subsequent communication is done via the secure socket. The method can only
  +     * be called once on a proxied secure connection.
  +     *
  +     * @throws IllegalStateException if connection is not secure and proxied or
  +     * if the socket is already secure.
  +     * @throws IOException if an error occured creating the secure socket
  +     */
  +    public void tunnelCreated() throws IllegalStateException, IOException {
  +        if (!isSecure() || !isProxied()) throw new IllegalStateException("Connection must
be secure and proxied to use this feature");
  +        if (_usingSecureSocket) throw new IllegalStateException("Already using a secure
socket");
  +
  +        if (sslSocketFactory == null) {
  +            sslSocketFactory = SSLSocketFactory.getDefault();
  +        }
  +        _socket = ((SSLSocketFactory) sslSocketFactory).createSocket(_socket, _host, _port,
true);
  +        _input = _socket.getInputStream();
  +        _output = _socket.getOutputStream();
  +        _usingSecureSocket = true;
  +        _tunnelEstablished = true;
  +        log.debug("Secure tunnel created");
  +    }
  +
  +
  +    /**
  +     * Indicates if the connection is completely transparent from end to end.
  +     *
  +     * @return true if conncetion is not proxied or tunneled through a transparent
  +     * proxy; false otherwise.
  +     */
  +    public boolean isTransparent() {
  +        return !isProxied() || _tunnelEstablished;
  +    }
  +
  +    /**
        * Return a {@link RequestOutputStream} suitable for writing (possibly 
        * chunked) bytes to my {@link OutputStream}.
        *
  @@ -405,6 +466,19 @@
           }
       }
   
  +    /**
  +     * Write the specified bytes to my output stream. The general contract for
  +     * write(b, off, len) is that some of the bytes in the array b are written
  +     * to the output stream in order; element b[off] is the first byte written
  +     * and b[off+len-1] is the last byte written by this operation.
  +     *
  +     * @param data array containing the data to be written.
  +     * @param off the start offset in the data.
  +     * @param len the number of bytes to write.
  +     */
  +    public void write(byte[] data, int off, int len) throws IOException {
  +        _output.write(data, off, len);
  +    }
   
       /**
        * Write the specified bytes, followed by <tt>"\r\n".getBytes()</tt> to
my
  @@ -597,6 +671,8 @@
           }
           _socket = null;
           _open = false;
  +        _tunnelEstablished = false;
  +        _usingSecureSocket = false;
       }
   
       /** 
  @@ -649,5 +725,10 @@
       private static final byte[] CRLF = "\r\n".getBytes();
       /** SO_TIMEOUT value */
       private int _so_timeout = 0;
  -
  +    /** An alternative factory for SSL sockets to use */
  +    private SocketFactory sslSocketFactory = null;
  +    /** Whether or not the _socket is a secure one. Note the difference to _ssl */
  +    private boolean _usingSecureSocket = false;
  +    /** Whether I am tunneling a proxy or not */
  +    private boolean _tunnelEstablished = false;
   }
  
  
  
  1.8       +70 -46    jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/HttpConnectionManager.java
  
  Index: HttpConnectionManager.java
  ===================================================================
  RCS file: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/HttpConnectionManager.java,v
  retrieving revision 1.7
  retrieving revision 1.8
  diff -u -r1.7 -r1.8
  --- HttpConnectionManager.java	22 Jul 2002 15:38:36 -0000	1.7
  +++ HttpConnectionManager.java	23 Jul 2002 14:38:31 -0000	1.8
  @@ -176,7 +176,9 @@
   
       /**
        * Return the port provided if not -1 (default), return 443 if the
  -     * protocol is HTTPS, otherwise 80
  +     * protocol is HTTPS, otherwise 80. 
  +     *
  +     * This functionality is a URLUtil and may be better off in URIUtils
        *
        * @param protocol the protocol to use to get the port for, e.g. http or
        *      https
  @@ -217,7 +219,7 @@
       {
           // FIXME: This method is too big
           if (sURL == null) {
  -            throw new MalformedURLException("sURL = null");
  +            throw new MalformedURLException("URL is null");
           }
   
           URL url = new URL(sURL);
  @@ -225,7 +227,7 @@
           String protocol = url.getProtocol();
           String host = url.getHost();
           int port = HttpConnectionManager.getPort(protocol, url.getPort());
  -        String hostAndPort = host + ":" + port;
  +        final String hostAndPort = host + ":" + port;
   
           if (log.isDebugEnabled()) {
               log.debug("HttpConnectionManager.getConnection:  key = "
  @@ -236,54 +238,23 @@
           LinkedList listConnections = getConnections(hostAndPort);
           
           HttpConnection conn = null;
  -        // get a connection from the 'pool', waiting for 'timeout' if none
  -        // are currently available
  +        // get a connection from the 'pool', waiting for 'timeout' if no
  +        // connections are currently available
           synchronized(listConnections) {
               if (listConnections.size() > 0) {
                   conn = (HttpConnection)listConnections.removeFirst();
               } else {
                   // get number of connections to host:port
  -                Integer numConnections = (Integer)mapNumConnections.get(hostAndPort);
  -                if (numConnections == null) {
  -                    log.error("HttpConnectionManager.getConnection:  "
  -                        + "No connection count for " + hostAndPort);
  -                    // This should never happen, but just in case we'll try to recover.
  -                    numConnections = new Integer(0);
  -                    mapNumConnections.put(hostAndPort, numConnections);
  -                }
  -                
  +                Integer numConnections =  getConnectionsInUse(hostAndPort);
                   if (numConnections.intValue() < maxConnections) {
                       // Create a new connection
  -                    if(log.isDebugEnabled()){
  -                        log.debug("HttpConnectionManager.getConnection:  "
  -                            + "creating connection for " + hostAndPort 
  -                            + " via " + proxyHost + ":" + proxyPort);
  -                    }
                       boolean isSecure = protocol.equalsIgnoreCase("HTTPS");
  -                    conn = new HttpConnection(proxyHost, proxyPort, host, port, isSecure);
  -                    numConnections = new Integer(numConnections.intValue()+1);
  +                    conn = new HttpConnection(proxyHost, proxyPort, host, port,
  +                        isSecure);
  +                    numConnections = new Integer(numConnections.intValue() + 1);
                       mapNumConnections.put(hostAndPort, numConnections);
                   } else {
  -                    // No connections available, so wait
  -                    // Start the timeout thread
  -                    TimeoutThread threadTimeout = new TimeoutThread();
  -                    threadTimeout.setTimeout(timeout);
  -                    threadTimeout.setWakeupThread(Thread.currentThread());
  -                    threadTimeout.start();
  -
  -                    // wait for the connection to be available
  -                    while(conn == null){    // spin lock
  -                        try {
  -                            log.debug("HttpConnectionManager.getConnection:  waiting for
connection for " + host + ":" + port);
  -                            listConnections.wait();
  -                        } catch (InterruptedException e) {
  -                            throw new HttpException("Timeout waiting for connection.");
  -                        }
  -                        if (listConnections.size() > 0) {
  -                            conn = (HttpConnection)listConnections.removeFirst();
  -                            threadTimeout.interrupt();
  -                        }
  -                    }
  +                    conn = waitForConnection(listConnections, timeout);
                   }
               }
           }
  @@ -292,7 +263,10 @@
       }
   
       /**
  -     * Get the list of connections available for the given host and port
  +     * Get the pool (list) of connections available for the given host and port
  +     *
  +     * @param hostAndPort the key for the connection pool
  +     * @return a pool (list) of connections available for the given key
        */
       private LinkedList getConnections(String hostAndPort) {
           // Look for a list of connections for the given host:port
  @@ -310,6 +284,56 @@
       }
       
       /**
  +     * Get the number of connections in use for the key
  +     *
  +     * @param hostAndPort the key that connections are tracked on
  +     * @return the number of connections in use for the given key
  +     */
  +    public Integer getConnectionsInUse(String hostAndPort) {
  +        // FIXME: Shouldn't this be synchronized on mapNumConnections? or
  +        //        mapHosts?
  +        Integer numConnections = (Integer)mapNumConnections.get(hostAndPort);
  +        if (numConnections == null) {
  +            log.error("HttpConnectionManager.getConnection:  "
  +                + "No connection count for " + hostAndPort);
  +            // This should never happen, but just in case we'll try to recover.
  +            numConnections = new Integer(0);
  +            mapNumConnections.put(hostAndPort, numConnections);
  +        }
  +        return numConnections;
  +    }
  +    
  +    /**
  +     * wait for a connection from the pool
  +     */
  +    private HttpConnection waitForConnection(LinkedList pool, long timeout)
  +    throws HttpException {
  +        // No connections available, so wait
  +        // Start the timeout thread
  +        TimeoutThread threadTimeout = new TimeoutThread();
  +        threadTimeout.setTimeout(timeout);
  +        threadTimeout.setWakeupThread(Thread.currentThread());
  +        threadTimeout.start();
  +
  +        HttpConnection conn = null;
  +        // wait for the connection to be available
  +        while(conn == null){    // spin lock
  +            try {
  +                log.debug("HttpConnectionManager.getConnection:  waiting for "
  +                    + "connection from " + pool);
  +                pool.wait();
  +            } catch (InterruptedException e) {
  +                throw new HttpException("Timeout waiting for connection.");
  +            }
  +            if (pool.size() > 0) {
  +                conn = (HttpConnection)pool.removeFirst();
  +                threadTimeout.interrupt();
  +            }
  +        }
  +        return conn;
  +    }
  +    
  +    /**
        * Make the given HttpConnection available for use by other requests.
        * If another thread is blocked in getConnection() waiting for a connection
        * for this host:port, they will be woken up.
  @@ -353,7 +377,7 @@
        * an outside mechanism to interrupt the waiting thread after the specified
        * timeout interval.
        */
  -    private class TimeoutThread extends Thread
  +    private static class TimeoutThread extends Thread
       {
           private long timeout = 0;
           private Thread thrdWakeup = null;
  
  
  
  1.35      +66 -15    jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/HttpMethodBase.java
  
  Index: HttpMethodBase.java
  ===================================================================
  RCS file: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/HttpMethodBase.java,v
  retrieving revision 1.34
  retrieving revision 1.35
  diff -u -r1.34 -r1.35
  --- HttpMethodBase.java	21 Jul 2002 04:12:45 -0000	1.34
  +++ HttpMethodBase.java	23 Jul 2002 14:38:31 -0000	1.35
  @@ -113,6 +113,7 @@
    * @author <a href="mailto:dion@apache.org">dIon Gillard</a>
    * @author <a href="mailto:jsdever@sympatico.ca">Jeff Dever</a>
    * @author <a href="mailto:dims@apache.org">Davanum Srinivas</a>
  + * @author Ortwin Glück
    * @version $Revision$ $Date$
    */
   public abstract class HttpMethodBase implements HttpMethod {
  @@ -280,7 +281,6 @@
        * Remove the request header associated with the given name.
        * Note that header-name matching is case insensitive.
        * @param headerName the header name
  -     * @return the header
        */
       public void removeRequestHeader(String headerName) {
           requestHeaders.remove(headerName.toLowerCase());
  @@ -441,6 +441,7 @@
        * @return the integer status code if one was obtained, or <tt>-1</tt>
        */
       public int execute(HttpState state, HttpConnection connection) throws HttpException,
IOException {
  +        // FIXME: This method is too large
           if (log.isDebugEnabled()) {
           	log.debug("enter HttpMethodBase.execute(HttpState, HttpConnection)");
           }
  @@ -463,9 +464,11 @@
   
           //pre-emptively add the authorization header, if required.
           Authenticator.authenticate(this, state);
  +        if (connection.isProxied()) Authenticator.authenticateProxy(this, state);
   
           Set visited = new HashSet();
           Set realms = new HashSet();
  +        Set proxyRealms = new HashSet();
           int retryCount = 0;
           for(;;) {
               visited.add(connection.getHost() + ":" + connection.getPort() + "|" + HttpMethodBase.generateRequestLine(connection,
getName(),getPath(),getQueryString(),(http11 ? "HTTP/1.1" : "HTTP/1.0")));
  @@ -508,10 +511,14 @@
               }
   
               if (!http11) {
  +                if (getName().equals(ConnectMethod.NAME) && (statusCode == HttpStatus.SC_OK))
{
  +                    log.debug("HttpMethodBase.execute(): leaving connection open for tunneling");
  +                } else {
                   if (log.isDebugEnabled()) {
                   	log.debug("HttpMethodBase.execute(): closing connection since we're using
HTTP/1.0");
                   }
                   connection.close();
  +                }
               } else {
                   Header connectionHeader = getResponseHeader("connection");
                   if (null != connectionHeader && "close".equalsIgnoreCase(connectionHeader.getValue()))
{
  @@ -522,23 +529,46 @@
                   }
               }
   
  -            if (HttpStatus.SC_UNAUTHORIZED == statusCode) {
  -                Header wwwauth = getResponseHeader("WWW-Authenticate");
  +            if ((HttpStatus.SC_UNAUTHORIZED == statusCode)
  +                || (HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED == statusCode)) {
  +
  +                Header wwwauth = null;
  +                Set realmsUsed = null;
  +                switch (statusCode) {
  +                    case HttpStatus.SC_UNAUTHORIZED:
  +                        wwwauth = getResponseHeader(Authenticator.WWW_AUTH);
  +                        realmsUsed = realms;
  +                        break;
  +
  +                    case HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED:
  +                        wwwauth = getResponseHeader(Authenticator.PROXY_AUTH);
  +                        realmsUsed = proxyRealms;
  +                        break;
  +                }
                   if (null != wwwauth) {
                       String pathAndCreds = getPath() + ":" + wwwauth.getValue();
  -                    if (realms.contains(pathAndCreds)) {
  +                    if (realmsUsed.contains(pathAndCreds)) {
                           if (log.isInfoEnabled()) {
  -                            log.info("Already tried to authenticate to \"" + wwwauth.getValue()
+ "\" but still receiving " + HttpStatus.SC_UNAUTHORIZED + ".");
  +                            log.info("Already tried to authenticate to \"" + wwwauth.getValue()
+ "\" but still receiving " + statusCode + ".");
                           }
                           break;
                       } else {
  -                        realms.add(pathAndCreds);
  +                        realmsUsed.add(pathAndCreds);
                       }
   
  -                    removeRequestHeader(Authenticator.WWW_AUTH_RESP); //remove preemptively
header
                       boolean authenticated = false;
                       try {
  +                        switch (statusCode) {
  +                            case HttpStatus.SC_UNAUTHORIZED:
  +                                removeRequestHeader(Authenticator.WWW_AUTH_RESP); //remove
preemptively header
                           authenticated = Authenticator.authenticate(this, state);
  +                                break;
  +
  +                            case HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED:
  +                                removeRequestHeader(Authenticator.PROXY_AUTH_RESP); //remove
preemptively header
  +                                authenticated = Authenticator.authenticateProxy(this,state);
  +                                break;
  +                        }
                       } catch (HttpException httpe) {
                           log.warn(httpe.getMessage());
                       } catch (UnsupportedOperationException uoe) {
  @@ -800,6 +830,7 @@
           addHostRequestHeader(state,conn);
           addCookieRequestHeader(state,conn);
           addAuthorizationRequestHeader(state,conn);
  +        addProxyAuthorizationRequestHeader(state, conn);
           addContentLengthRequestHeader(state,conn);
       }
   
  @@ -857,8 +888,8 @@
        */
       protected void addAuthorizationRequestHeader(HttpState state, HttpConnection conn)
throws IOException, HttpException {
           // add authorization header, if needed
  -        if (!requestHeaders.containsKey("authorization")) {
  -            Header wwwAuthenticateHeader = (Header)(responseHeaders.get("www-authenticate"));
  +        if (!requestHeaders.containsKey(Authenticator.WWW_AUTH_RESP.toLowerCase())) {
  +            Header wwwAuthenticateHeader = (Header)(responseHeaders.get(Authenticator.WWW_AUTH.toLowerCase()));
               if (null != wwwAuthenticateHeader) {
                   try {
                       Authenticator.authenticate(this,state);
  @@ -870,6 +901,25 @@
       }
   
       /**
  +     * Adds a <tt>Proxy-Authorization</tt> request if needed,
  +     * as long as no <tt>Proxy-Authorization</tt> request header
  +     * already exists.
  +     */
  +    protected void addProxyAuthorizationRequestHeader(HttpState state, HttpConnection conn)
throws IOException, HttpException {
  +        // add authorization header, if needed
  +        if (!requestHeaders.containsKey(Authenticator.PROXY_AUTH_RESP.toLowerCase())) {
  +            Header wwwAuthenticateHeader = (Header)(responseHeaders.get(Authenticator.PROXY_AUTH.toLowerCase()));
  +            if (null != wwwAuthenticateHeader) {
  +                try {
  +                    Authenticator.authenticateProxy(this,state);
  +                } catch(HttpException e) {
  +                    // ignored
  +                }
  +            }
  +        }
  +    }
  +
  +    /**
        * Adds a <tt>Content-Length</tt> or
        * <tt>Transer-Encoding: Chunked</tt> request header,
        * as long as no <tt>Content-Length</tt> request header
  @@ -1239,13 +1289,14 @@
                   if ("chunked".equalsIgnoreCase(transferEncodingHeader.getValue())) {
                       expectedLength = -1;
                   }
  -            } else if(canResponseHaveBody(statusCode)){
  +            } else if(canResponseHaveBody(statusCode) && !getName().equals(ConnectMethod.NAME)){
                   /*
                    * According to the specification, a response with neither Content-Length
                    * nor Transfer-Encoding indicates that the response has no body.  In
                    * the real world, this usually means that the server just didn't know
                    * the content-length when it sent the response.  FIXME:  Should we do
                    * this only in non-strict mode?
  +                 * If we do this we will hang forever waiting for a body!
                    */
                   expectedLength = -1;
               }
  @@ -1368,7 +1419,7 @@
               buf.append(qString);
           }
   
  -        if (!connection.isProxied()) {
  +        if (!connection.isProxied() || connection.isTransparent()) {
               return (name + " " + buf.toString() + " " + protocol + "\r\n");
           } else {
               if (connection.isSecure()) {
  
  
  
  1.11      +15 -5     jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/HttpMultiClient.java
  
  Index: HttpMultiClient.java
  ===================================================================
  RCS file: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/HttpMultiClient.java,v
  retrieving revision 1.10
  retrieving revision 1.11
  diff -u -r1.10 -r1.11
  --- HttpMultiClient.java	22 Jul 2002 15:36:18 -0000	1.10
  +++ HttpMultiClient.java	23 Jul 2002 14:38:31 -0000	1.11
  @@ -90,8 +90,11 @@
       private HttpSharedState state = null;
       /** manager of http connections on a host:port basis */
       private HttpConnectionManager mgr = new HttpConnectionManager();
  +    /** specifies strict HTTP compliance */
       private boolean strictMode = true;
  +    /** how long to wait for a connection to become available */
       private int timeoutConnection = 0;
  +    /** how long to wait for a request to complete */
       private int timeoutRequest = 0;
   
       // ----------------------------------------------------------- Constructors
  @@ -108,6 +111,9 @@
       /**
        * Constructor that uses proxy host and port. Strict mode is enabled by 
        * default.
  +     *
  +     * @param proxyHost host name of the proxy server to use
  +     * @param proxyPort port number of the proxy server to use
        */
       public HttpMultiClient(String proxyHost, int proxyPort) {
           this();
  @@ -291,8 +297,12 @@
               mgr.releaseConnection(connection);
           }
   
  -        if (status == 301 || status == 302
  -            || status == 303 || status == 307) {
  +        // FIXME: Why is this different from the redirect processing in
  +        //        HttpMethodBase
  +        if (status == HttpStatus.SC_MOVED_PERMANENTLY 
  +            || status == HttpStatus.SC_MOVED_TEMPORARILY
  +            || status == HttpStatus.SC_SEE_OTHER
  +            || status == HttpStatus.SC_TEMPORARY_REDIRECT) {
               Header header = method.getResponseHeader("Location");
               String url = header.getValue();
               if (url == null) {
  
  
  
  1.1                  jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/ConnectMethod.java
  
  Index: ConnectMethod.java
  ===================================================================
  /*
   * $Header: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/ConnectMethod.java,v
1.1 2002/07/23 14:38:31 dion Exp $
   * $Revision: 1.1 $
   * $Date: 2002/07/23 14:38:31 $
   * ====================================================================
   *
   * The Apache Software License, Version 1.1
   *
   * Copyright (c) 1999 The Apache Software Foundation.  All rights
   * reserved.
   *
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions
   * are met:
   *
   * 1. Redistributions of source code must retain the above copyright
   *    notice, this list of conditions and the following disclaimer.
   *
   * 2. Redistributions in binary form must reproduce the above copyright
   *    notice, this list of conditions and the following disclaimer in
   *    the documentation and/or other materials provided with the
   *    distribution.
   *
   * 3. The end-user documentation included with the redistribution, if
   *    any, must include the following acknowlegement:
   *       "This product includes software developed by the
   *        Apache Software Foundation (http://www.apache.org/)."
   *    Alternately, this acknowlegement may appear in the software itself,
   *    if and wherever such third-party acknowlegements normally appear.
   *
   * 4. The names "The Jakarta Project", "HttpClient", and "Apache Software
   *    Foundation" must not be used to endorse or promote products derived
   *    from this software without prior written permission. For written
   *    permission, please contact apache@apache.org.
   *
   * 5. Products derived from this software may not be called "Apache"
   *    nor may "Apache" appear in their names without prior written
   *    permission of the Apache Group.
   *
   * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
   * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
   * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
   * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
   * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
   * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
   * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
   * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
   * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
   * SUCH DAMAGE.
   * ====================================================================
   *
   * This software consists of voluntary contributions made by many
   * individuals on behalf of the Apache Software Foundation.  For more
   * information on the Apache Software Foundation, please see
   * <http://www.apache.org/>.
   *
   * [Additional notices, if required by prior licensing conditions]
   *
   */
  
  package org.apache.commons.httpclient;
  
  import java.io.IOException;
  
  import org.apache.commons.logging.Log;
  import org.apache.commons.logging.LogFactory;
  
  /**
   * <p>Wraps another method to tunnel through a proxy.</p>
   * @author Ortwin Glück
   * @version $Revision: 1.1 $ $Date: 2002/07/23 14:38:31 $
   */
  
  public class ConnectMethod extends HttpMethodBase {
      public static final String NAME = "CONNECT";
  
      /**
       * Create a connect method wrapping the existing method
       */
      public ConnectMethod(HttpMethod method) {
          this.method = method;
      }
  
      public String getName() {
          return NAME;
      }
  
      public int execute(HttpState state, HttpConnection connection) throws IOException, HttpException
{
          int code = super.execute(state,  connection);
          log.debug("CONNECT status code "+ code);
          if ((code >= 200) && (code < 300)) {
              connection.tunnelCreated();
              code = method.execute(state, connection);
          }
          return code;
      }
  
      /**
       * Writes a minimal set of headers to the proxy.
       */
      protected void writeRequestHeaders(HttpState state, HttpConnection conn) throws HttpException,
IOException {
          if (method instanceof HttpMethodBase) {
              ((HttpMethodBase) method).addRequestHeaders(state, conn);
          }
          conn.print(method.getRequestHeader("Host").toExternalForm());
          Header header = method.getRequestHeader(Authenticator.PROXY_AUTH_RESP);
          if (header == null) header = getRequestHeader(Authenticator.PROXY_AUTH_RESP);
          if (header != null) conn.print(header.toExternalForm());
      }
  
      /**
       * Special Connect request
       */
      protected void writeRequestLine(HttpState state, HttpConnection conn) throws IOException,
HttpException {
          int port = conn.getPort();
          if (port == -1) {
              port = conn.isSecure() ? 443 : 80;
          }
          String line = getName() +" "+ conn.getHost() +":"+ port +" HTTP/1.1";
          conn.printLine(line);
      }
  
      /** the log for output */
      static private final Log log = LogFactory.getLog(ConnectMethod.class);
      /** The wrapped method */
      private HttpMethod method;
  }
  
  
  1.12      +9 -7      jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/methods/GetMethod.java
  
  Index: GetMethod.java
  ===================================================================
  RCS file: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/methods/GetMethod.java,v
  retrieving revision 1.11
  retrieving revision 1.12
  diff -u -r1.11 -r1.12
  --- GetMethod.java	21 Jul 2002 04:12:46 -0000	1.11
  +++ GetMethod.java	23 Jul 2002 14:38:31 -0000	1.12
  @@ -195,9 +195,10 @@
   
   
       /**
  -     * Use disk setter.
  +     * Buffer the response in a file or not. The default is false.
        *
  -     * @param useDisk New value of useDisk
  +     * @param useDisk If true the entire response will be buffered in a
  +     * temporary file.
        */
       public void setUseDisk(boolean useDisk) {
           checkNotUsed();
  @@ -206,9 +207,10 @@
   
   
       /**
  -     * Use disk getter.
  +     * Tells if the response will be buffered in a file.
        *
  -     * @param boolean useDisk value
  +     * @param boolean If true the entire response will be buffered in a
  +     * temporary file.
        */
       public boolean getUseDisk() {
           return useDisk;
  
  
  
  1.13      +156 -14   jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/methods/PostMethod.java
  
  Index: PostMethod.java
  ===================================================================
  RCS file: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/methods/PostMethod.java,v
  retrieving revision 1.12
  retrieving revision 1.13
  diff -u -r1.12 -r1.13
  --- PostMethod.java	21 Jul 2002 04:12:46 -0000	1.12
  +++ PostMethod.java	23 Jul 2002 14:38:31 -0000	1.13
  @@ -71,6 +71,10 @@
   import org.apache.commons.logging.Log;
   import org.apache.commons.logging.LogFactory;
   
  +import java.util.Vector;
  +import java.io.ByteArrayInputStream;
  +import java.io.ByteArrayOutputStream;
  +import java.io.InputStream;
   import java.io.IOException;
   import java.util.Iterator;
   import java.util.List;
  @@ -96,9 +100,21 @@
    * @author <a href="mailto:jsdever@sympatico.ca">Jeffrey Dever</a>
    * @author <a href="mailto:remm@apache.org">Remy Maucherat</a>
    * @author <a href="mailto:dsale@us.britannica.com">Doug Sale</a>
  + * @author Ortwin Glück
    */
   public class PostMethod extends GetMethod {
  +    /**
  +     * The content length will be calculated automatically. This implies buffering
  +     * of the content.
  +     */
  +    public static final int CONTENT_LENGTH_AUTO = -2;
   
  +    /**
  +     * The request will use chunked transfer encoding. Content length is not
  +     * calculated and the content is not buffered.<br>
  +     * Note: Chunked requests are not supported at the moment.
  +     */
  +    public static final int CONTENT_LENGTH_CHUNKED = -1;
   
       // ----------------------------------------------------------- Constructors
   
  @@ -107,6 +123,7 @@
        */
       public PostMethod() {
           super();
  +        setFollowRedirects(false);
       }
   
       /**
  @@ -115,6 +132,7 @@
        */
       public PostMethod(String path) {
           super(path);
  +        setFollowRedirects(false);
       }
   
       /**
  @@ -124,6 +142,7 @@
        */
       public PostMethod(String path, String tempDir) {
           super(path, tempDir);
  +        setFollowRedirects(false);
       }
   
       /**
  @@ -134,6 +153,7 @@
        */
       public PostMethod(String path, String tempDir, String tempFile) {
           super(path, tempDir, tempFile);
  +        setFollowRedirects(false);
       }
   
   
  @@ -148,12 +168,26 @@
       }
   
       /**
  +     * A POST request can only be redirected if input is buffered.
  +     * Overrides method of {@link HttpMethodBase}.
  +     * @return true if request is buffered and <code>setFollowRedirects</code>
  +     * was set to <code>true</code>.
  +     */
  +    public boolean getFollowRedirects() {
  +        if (!super.getFollowRedirects()) return false;
  +        return (buffer == null);
  +    }
  +
  +
  +    /**
        * Override method of {@link HttpMethodBase}
        * to clear my request body.
        */
       public void recycle() {
           super.recycle();
           requestBody = null;
  +        requestContentLength = CONTENT_LENGTH_AUTO;
  +        buffer = null;
           parameters.clear();
       }
   
  @@ -173,6 +207,30 @@
       }
   
       /**
  +     * Sets length information about the request body.
  +     * <p>Note: If you specify a content length the request is unbuffered. This
  +     * prevents automatic retry if a request fails the first time. This means
  +     * that the HttpClient can not perform authorization automatically but will
  +     * throw an Exception. You will have to set the necessary 'Authorization' or
  +     * 'Proxy-Authorization' headers manually.</p>
  +     *
  +     * @param length size in bytes or any of CONTENT_LENGTH_AUTO,
  +     * CONTENT_LENGTH_CHUNKED. If number of bytes is specified the content will
  +     * not be buffered internally and the Content-Length header of the request
  +     * will be used. In this case the user is responsible to supply the correct
  +     * content length.
  +     *
  +     *
  +     *
  +     */
  +    public void setRequestContentLength(int length) {
  +        if ((length == CONTENT_LENGTH_CHUNKED) && !isHttp11()) {
  +            throw new RuntimeException("Chunked transfer encoding not allowed for HTTP/1.0");
  +        }
  +        requestContentLength = length;
  +    }
  +
  +    /**
        * Add a new parameter to be used in the POST request body.
        *
        * @param paramName The parameter name to add.
  @@ -237,6 +295,7 @@
   
       /**
        * Gets the parameter of the specified name.
  +     *
        * If there exists more than one parameter with the name paramName,
        * then only the first one is returned.
        * 
  @@ -260,6 +319,7 @@
   
       /**
        * Gets the parameters currently added to the PostMethod.
  +     *
        * If there are no parameters, a valid array is returned with
        * zero elements.
        * The returned array object contains an array of pointers to 
  @@ -344,11 +404,20 @@
        * cannot be altered until I am {@link #recycle recycled}.
        * 
        * @throws IllegalStateException if request params have been added
  +     * @deprecated This method converts characters to bytes in a platform dependent
  +     * encoding. Use setRequestBody(InputStream) instead.
        */
       public void setRequestBody(String body) {
           if(!parameters.isEmpty()) {
               throw new IllegalStateException("Request parameters have already been added.");
           }
  +        requestBody = new ByteArrayInputStream(body.getBytes());
  +    }
  +
  +    public void setRequestBody(InputStream body) {
  +        if(!parameters.isEmpty()) {
  +            throw new IllegalStateException("Request parameters have already been added.");
  +        }
           requestBody = body;
       }
   
  @@ -359,15 +428,29 @@
        * request body from the paramters if they exist.  Null otherwise.
        * @since 2.0
        */
  -    public String getRequestBody() {
  -        if(requestBody != null){
  +    public InputStream getRequestBody() {
  +        if (requestBody != null) {
               return requestBody;
  -        }else if (!parameters.isEmpty()){
  +        } else if (!parameters.isEmpty()) {
               return generateRequestBody(parameters);
  -        }else{
  +        } else {
               return null;
           }
       }
  +    
  +    /**
  +     *@return the request body as a string
  +     */
  +    public String getRequestBodyAsString() throws IOException{
  +        StringBuffer buffer = new StringBuffer();
  +        InputStream requestBody = getRequestBody();
  +        int data = requestBody.read();
  +        while (data != -1) {
  +            buffer.append((char) data);
  +            data = requestBody.read();
  +        }
  +        return buffer.toString();
  +    }
   
   
       /**
  @@ -385,6 +468,9 @@
        * Override method of {@link HttpMethodBase}
        * to write request parameters as the request body.
        * 
  +     * The input stream will be truncated after the specified content length.
  +     * @throws IOException if the stream ends before the specified content length.
  +     *
        * <p>Once this method has been invoked,  the request parameters 
        * cannot be altered until I am {@link #recycle recycled}.
        *
  @@ -395,7 +481,31 @@
           if(null == requestBody) {
               requestBody = generateRequestBody(parameters);
           }
  -        conn.print(requestBody);
  +        if ((repeatCount > 0) && (buffer == null)) {
  +            throw new HttpException("Sorry, unbuffered POST request can not be repeated.");
  +        }
  +        repeatCount++;
  +
  +        byte[] data = new byte[10000];
  +        int l = requestBody.read(data);
  +        int total = 0;
  +        while (l > 0) {
  +            if ((requestContentLength > 0) && (total + l > requestContentLength))
{
  +                l = requestContentLength - total;
  +                conn.write(data, 0, l);
  +                break;
  +            }
  +            conn.write(data, 0, l);
  +            total += l;
  +            l = requestBody.read(data);
  +        }
  +        if ((requestContentLength > 0) && (total < requestContentLength))
{
  +            throw new IOException("unexpected end of input stream");
  +        }
  +        if (buffer != null) {
  +            //restore buffered content for repeated requests
  +            requestBody = new ByteArrayInputStream(buffer.toByteArray());
  +        }
           return true;
       }
   
  @@ -408,10 +518,41 @@
       protected int getRequestContentLength() {
           if(null == requestBody) {
               requestBody = generateRequestBody(parameters);
  +            bufferContent();
           }
  -        return requestBody.getBytes().length;
  +
  +        if (requestContentLength != CONTENT_LENGTH_AUTO) {
  +            return requestContentLength;
  +        }
  +
  +        bufferContent();
  +
  +        return requestContentLength;
       }
   
  +    /**
  +     * Buffers the request body and calculates the content length.
  +     * If the method was called earlier it returns immediately.
  +     */
  +    private void bufferContent() {
  +        if (buffer != null) return;
  +        try {
  +            buffer = new ByteArrayOutputStream();
  +            byte[] data = new byte[10000];
  +            int l = requestBody.read(data);
  +            int total = 0;
  +            while (l > 0) {
  +                buffer.write(data, 0, l);
  +                total += l;
  +                l = requestBody.read(data);
  +            }
  +            requestBody = new ByteArrayInputStream(buffer.toByteArray());
  +            requestContentLength = total;
  +        } catch(IOException e) {
  +            requestBody = null;
  +            requestContentLength = 0;
  +        }
  +    }
   
       // -------------------------------------------------------------- Class Methods
   
  @@ -421,7 +562,7 @@
        * TODO: consider moving this out into URIUtil.
        * @return urlencoded string
        */
  -    static String generateRequestBody(List params) {
  +    static InputStream generateRequestBody(List params) {
           Iterator it = params.iterator();
           StringBuffer sb = new StringBuffer();
           while(it.hasNext()) {
  @@ -434,15 +575,16 @@
                   sb.append("&");
               }
           }
  -        return sb.toString();
  +        return new ByteArrayInputStream(sb.toString().getBytes());
       }
   
  -
       // -------------------------------------------------------------- Instance Variables

   
  -    private String requestBody = null;
  +    private InputStream requestBody = null;
       private Vector parameters = new Vector();
  -
  +    private int requestContentLength = CONTENT_LENGTH_AUTO;
  +    private ByteArrayOutputStream buffer = null;
  +    private int repeatCount = 0;
   
       // -------------------------------------------------------------- Constants
   
  
  
  
  1.11      +5 -3      jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/methods/PutMethod.java
  
  Index: PutMethod.java
  ===================================================================
  RCS file: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/methods/PutMethod.java,v
  retrieving revision 1.10
  retrieving revision 1.11
  diff -u -r1.10 -r1.11
  --- PutMethod.java	21 Jul 2002 04:12:46 -0000	1.10
  +++ PutMethod.java	23 Jul 2002 14:38:31 -0000	1.11
  @@ -96,6 +96,7 @@
        * No-arg constructor.
        */
       public PutMethod() {
  +        setFollowRedirects(false);
       }
   
   
  @@ -105,6 +106,7 @@
        */
       public PutMethod(String path) {
           super(path);
  +        setFollowRedirects(false);
       }
   
   
  
  
  

--
To unsubscribe, e-mail:   <mailto:commons-dev-unsubscribe@jakarta.apache.org>
For additional commands, e-mail: <mailto:commons-dev-help@jakarta.apache.org>


Mime
View raw message