/* * 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; } }}
/*
* 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;
}
}
}