476 lines · 17.7 KB · Java
RawDownload
/* * Copyright (c) 1996, 2008, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation.  Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code 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 General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */package sun.rmi.transport.proxy; import java.io.*;import java.net.*;import java.security.*;import java.util.*;import java.rmi.server.LogStream;import java.rmi.server.RMISocketFactory;import sun.rmi.runtime.Log;import sun.rmi.runtime.NewThreadAction;import sun.security.action.GetBooleanAction;import sun.security.action.GetLongAction; /** * RMIMasterSocketFactory attempts to create a socket connection to the * specified host using successively less efficient mechanisms * until one succeeds.  If the host is successfully connected to, * the factory for the successful mechanism is stored in an internal * hash table keyed by the host name, so that future attempts to * connect to the same host will automatically use the same * mechanism. */public class RMIMasterSocketFactory extends RMISocketFactory {     /** "proxy" package log level */    static int logLevel = LogStream.parseLevel(getLogLevel());     private static String getLogLevel() {        return java.security.AccessController.doPrivileged(            new sun.security.action.GetPropertyAction("sun.rmi.transport.proxy.logLevel"));    }     /* proxy package log */    static final Log proxyLog =        Log.getLog("sun.rmi.transport.tcp.proxy",                   "transport", RMIMasterSocketFactory.logLevel);     /** timeout for attemping direct socket connections */    private static long connectTimeout = getConnectTimeout();     private static long getConnectTimeout() {        return java.security.AccessController.doPrivileged(                new GetLongAction("sun.rmi.transport.proxy.connectTimeout",                              15000)).longValue(); // default: 15 seconds    }     /** whether to fallback to HTTP on general connect failures */    private static final boolean eagerHttpFallback =        java.security.AccessController.doPrivileged(new GetBooleanAction(            "sun.rmi.transport.proxy.eagerHttpFallback")).booleanValue();     /** table of hosts successfully connected to and the factory used */    private Hashtable successTable = new Hashtable();     /** maximum number of hosts to remember successful connection to */    private static final int MaxRememberedHosts = 64;     /** list of the hosts in successTable in initial connection order */    private Vector hostList = new Vector(MaxRememberedHosts);     /** default factory to initally use for direct socket connection */    protected RMISocketFactory initialFactory = new RMIDirectSocketFactory();     /** ordered list of factories to try as alternate connection      * mechanisms if a direct socket connections fails */    protected Vector altFactoryList;     /**     * Create a RMIMasterSocketFactory object.  Establish order of     * connection mechanisms to attempt on createSocket, if a direct     * socket connection fails.     */    public RMIMasterSocketFactory() {        altFactoryList = new Vector(2);        boolean setFactories = false;         try {            String proxyHost;            proxyHost = java.security.AccessController.doPrivileged(                new sun.security.action.GetPropertyAction("http.proxyHost"));             if (proxyHost == null)                proxyHost = java.security.AccessController.doPrivileged(                    new sun.security.action.GetPropertyAction("proxyHost"));             Boolean tmp = java.security.AccessController.doPrivileged(                new sun.security.action.GetBooleanAction("java.rmi.server.disableHttp"));             if (!tmp.booleanValue() &&                (proxyHost != null && proxyHost.length() > 0)) {                setFactories = true;            }        } catch (Exception e) {            // unable to obtain the properties, so assume default behavior.            setFactories = true;        }         if (setFactories) {            altFactoryList.addElement(new RMIHttpToPortSocketFactory());            altFactoryList.addElement(new RMIHttpToCGISocketFactory());        }    }     /**     * Create a new client socket.  If we remember connecting to this host     * successfully before, then use the same factory again.  Otherwise,     * try using a direct socket connection and then the alternate factories     * in the order specified in altFactoryList.     */    public Socket createSocket(String host, int port)        throws IOException    {        if (proxyLog.isLoggable(Log.BRIEF)) {            proxyLog.log(Log.BRIEF, "host: " + host + ", port: " + port);        }         /*         * If we don't have any alternate factories to consult, short circuit         * the fallback procedure and delegate to the initial factory.         */        if (altFactoryList.size() == 0) {            return initialFactory.createSocket(host, port);        }         RMISocketFactory factory;         /*         * If we remember successfully connecting to this host before,         * use the same factory.         */        factory = (RMISocketFactory) successTable.get(host);        if (factory != null) {            if (proxyLog.isLoggable(Log.BRIEF)) {                proxyLog.log(Log.BRIEF,                    "previously successful factory found: " + factory);            }            return factory.createSocket(host, port);        }         /*         * Next, try a direct socket connection.  Open socket in another         * thread and only wait for specified timeout, in case the socket         * would otherwise spend minutes trying an unreachable host.         */        Socket initialSocket = null;        Socket fallbackSocket = null;        final AsyncConnector connector =            new AsyncConnector(initialFactory, host, port,                AccessController.getContext());                // connection must be attempted with                // this thread's access control context        IOException initialFailure = null;         try {            synchronized (connector) {                 Thread t = java.security.AccessController.doPrivileged(                    new NewThreadAction(connector, "AsyncConnector", true));                t.start();                 try {                    long now = System.currentTimeMillis();                    long deadline = now + connectTimeout;                    do {                        connector.wait(deadline - now);                        initialSocket = checkConnector(connector);                        if (initialSocket != null)                            break;                        now = System.currentTimeMillis();                    } while (now < deadline);                } catch (InterruptedException e) {                    throw new InterruptedIOException(                        "interrupted while waiting for connector");                }            }             // assume no route to host (for now) if no connection yet            if (initialSocket == null)                throw new NoRouteToHostException(                    "connect timed out: " + host);             proxyLog.log(Log.BRIEF, "direct socket connection successful");             return initialSocket;         } catch (UnknownHostException e) {            initialFailure = e;        } catch (NoRouteToHostException e) {            initialFailure = e;        } catch (SocketException e) {            if (eagerHttpFallback) {                initialFailure = e;            } else {                throw e;            }        } finally {            if (initialFailure != null) {                 if (proxyLog.isLoggable(Log.BRIEF)) {                    proxyLog.log(Log.BRIEF,                        "direct socket connection failed: ", initialFailure);                }                 // Finally, try any alternate connection mechanisms.                for (int i = 0; i < altFactoryList.size(); ++ i) {                    factory = (RMISocketFactory) altFactoryList.elementAt(i);                    try {                        if (proxyLog.isLoggable(Log.BRIEF)) {                            proxyLog.log(Log.BRIEF,                                "trying with factory: " + factory);                        }                         // For HTTP connections, the output (POST request) must                        // be sent before we verify a successful connection.                        // So, sacrifice a socket for the sake of testing...                        // The following sequence should verify a successful                        // HTTP connection if no IOException is thrown.                        Socket testSocket = factory.createSocket(host, port);                        InputStream in = testSocket.getInputStream();                        int b = in.read(); // probably -1 for EOF...                        testSocket.close();                    } catch (IOException ex) {                        if (proxyLog.isLoggable(Log.BRIEF)) {                            proxyLog.log(Log.BRIEF, "factory failed: ", ex);                        }                         continue;                    }                    proxyLog.log(Log.BRIEF, "factory succeeded");                     // factory succeeded, open new socket for caller's use                    try {                        fallbackSocket = factory.createSocket(host, port);                    } catch (IOException ex) {  // if it fails 2nd time,                    }                           // just give up                    break;                }            }        }         synchronized (successTable) {            try {                // check once again to see if direct connection succeeded                synchronized (connector) {                    initialSocket = checkConnector(connector);                }                if (initialSocket != null) {                    // if we had made another one as well, clean it up...                    if (fallbackSocket != null)                        fallbackSocket.close();                    return initialSocket;                }                // if connector ever does get socket, it won't be used                connector.notUsed();            } catch (UnknownHostException e) {                initialFailure = e;            } catch (NoRouteToHostException e) {                initialFailure = e;            } catch (SocketException e) {                if (eagerHttpFallback) {                    initialFailure = e;                } else {                    throw e;                }            }            // if we had found an alternate mechanism, go and use it            if (fallbackSocket != null) {                // remember this successful host/factory pair                rememberFactory(host, factory);                return fallbackSocket;            }            throw initialFailure;        }    }     /**     * Remember a successful factory for connecting to host.     * Currently, excess hosts are removed from the remembered list     * using a Least Recently Created strategy.     */    void rememberFactory(String host, RMISocketFactory factory) {        synchronized (successTable) {            while (hostList.size() >= MaxRememberedHosts) {                successTable.remove(hostList.elementAt(0));                hostList.removeElementAt(0);            }            hostList.addElement(host);            successTable.put(host, factory);        }    }     /**     * Check if an AsyncConnector succeeded.  If not, return socket     * given to fall back to.     */    Socket checkConnector(AsyncConnector connector)        throws IOException    {        Exception e = connector.getException();        if (e != null) {            e.fillInStackTrace();            /*             * The AsyncConnector implementation guaranteed that the exception             * will be either an IOException or a RuntimeException, and we can             * only throw one of those, so convince that compiler that it must             * be one of those.             */            if (e instanceof IOException) {                throw (IOException) e;            } else if (e instanceof RuntimeException) {                throw (RuntimeException) e;            } else {                throw new Error("internal error: " +                    "unexpected checked exception: " + e.toString());            }        }        return connector.getSocket();    }     /**     * Create a new server socket.     */    public ServerSocket createServerSocket(int port) throws IOException {        //return new HttpAwareServerSocket(port);        return initialFactory.createServerSocket(port);    }      /**     * AsyncConnector is used by RMIMasterSocketFactory to attempt socket     * connections on a separate thread.  This allows RMIMasterSocketFactory     * to control how long it will wait for the connection to succeed.     */    private class AsyncConnector implements Runnable {         /** what factory to use to attempt connection */        private RMISocketFactory factory;         /** the host to connect to */        private String host;         /** the port to connect to */        private int port;         /** access control context to attempt connection within */        private AccessControlContext acc;         /** exception that occurred during connection, if any */        private Exception exception = null;         /** the connected socket, if successful */        private Socket socket = null;         /** socket should be closed after created, if ever */        private boolean cleanUp = false;         /**         * Create a new asynchronous connector object.         */        AsyncConnector(RMISocketFactory factory, String host, int port,                       AccessControlContext acc)        {            this.factory = factory;            this.host    = host;            this.port    = port;            this.acc     = acc;            SecurityManager security = System.getSecurityManager();            if (security != null) {                security.checkConnect(host, port);            }        }         /**         * Attempt socket connection in separate thread.  If successful,         * notify master waiting,         */        public void run() {            try {                /*                 * Using the privileges of the thread that wants to make the                 * connection is tempting, but it will fail with applets with                 * the current applet security manager because the applet                 * network connection policy is not captured in the permission                 * framework of the access control context we have.                 *                 * java.security.AccessController.beginPrivileged(acc);                 */                try {                    Socket temp = factory.createSocket(host, port);                    synchronized (this) {                        socket = temp;                        notify();                    }                    rememberFactory(host, factory);                    synchronized (this) {                        if (cleanUp)                          try {                              socket.close();                          } catch (IOException e) {                          }                    }                } catch (Exception e) {                    /*                     * Note that the only exceptions which could actually have                     * occurred here are IOException or RuntimeException.                     */                    synchronized (this) {                        exception = e;                        notify();                    }                }            } finally {                /*                 * See above comments for matching beginPrivileged() call that                 * is also commented out.                 *                 * java.security.AccessController.endPrivileged();                 */            }        }         /**         * Get exception that occurred during connection attempt, if any.         * In the current implementation, this is guaranteed to be either         * an IOException or a RuntimeException.         */        private synchronized Exception getException() {            return exception;        }         /**         * Get successful socket, if any.         */        private synchronized Socket getSocket() {            return socket;        }         /**         * Note that this connector's socket, if ever successfully created,         * will not be used, so it should be cleaned up quickly         */        synchronized void notUsed() {            if (socket != null) {                try {                    socket.close();                } catch (IOException e) {                }            }            cleanUp = true;        }    }}