jdk/src/java.rmi/share/classes/sun/rmi/transport/proxy/RMIMasterSocketFactory.java
changeset 37947 f69560487686
parent 37946 e420b9f05aaf
parent 37936 428ebc487445
child 37948 caf97b37ebec
equal deleted inserted replaced
37946:e420b9f05aaf 37947:f69560487686
     1 /*
       
     2  * Copyright (c) 1996, 2013, Oracle and/or its affiliates. All rights reserved.
       
     3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
       
     4  *
       
     5  * This code is free software; you can redistribute it and/or modify it
       
     6  * under the terms of the GNU General Public License version 2 only, as
       
     7  * published by the Free Software Foundation.  Oracle designates this
       
     8  * particular file as subject to the "Classpath" exception as provided
       
     9  * by Oracle in the LICENSE file that accompanied this code.
       
    10  *
       
    11  * This code is distributed in the hope that it will be useful, but WITHOUT
       
    12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
       
    13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
       
    14  * version 2 for more details (a copy is included in the LICENSE file that
       
    15  * accompanied this code).
       
    16  *
       
    17  * You should have received a copy of the GNU General Public License version
       
    18  * 2 along with this work; if not, write to the Free Software Foundation,
       
    19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
       
    20  *
       
    21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
       
    22  * or visit www.oracle.com if you need additional information or have any
       
    23  * questions.
       
    24  */
       
    25 package sun.rmi.transport.proxy;
       
    26 
       
    27 import java.io.*;
       
    28 import java.net.*;
       
    29 import java.security.*;
       
    30 import java.util.*;
       
    31 import java.rmi.server.LogStream;
       
    32 import java.rmi.server.RMISocketFactory;
       
    33 import sun.rmi.runtime.Log;
       
    34 import sun.rmi.runtime.NewThreadAction;
       
    35 
       
    36 /**
       
    37  * RMIMasterSocketFactory attempts to create a socket connection to the
       
    38  * specified host using successively less efficient mechanisms
       
    39  * until one succeeds.  If the host is successfully connected to,
       
    40  * the factory for the successful mechanism is stored in an internal
       
    41  * hash table keyed by the host name, so that future attempts to
       
    42  * connect to the same host will automatically use the same
       
    43  * mechanism.
       
    44  */
       
    45 @SuppressWarnings("deprecation")
       
    46 public class RMIMasterSocketFactory extends RMISocketFactory {
       
    47 
       
    48     /** "proxy" package log level */
       
    49     static int logLevel = LogStream.parseLevel(getLogLevel());
       
    50 
       
    51     private static String getLogLevel() {
       
    52         return java.security.AccessController.doPrivileged(
       
    53             (PrivilegedAction<String>) () -> System.getProperty("sun.rmi.transport.proxy.logLevel"));
       
    54     }
       
    55 
       
    56     /* proxy package log */
       
    57     static final Log proxyLog =
       
    58         Log.getLog("sun.rmi.transport.tcp.proxy",
       
    59                    "transport", RMIMasterSocketFactory.logLevel);
       
    60 
       
    61     /** timeout for attemping direct socket connections */
       
    62     private static long connectTimeout = getConnectTimeout();
       
    63 
       
    64     private static long getConnectTimeout() {
       
    65         return java.security.AccessController.doPrivileged((PrivilegedAction<Long>) () ->
       
    66             Long.getLong("sun.rmi.transport.proxy.connectTimeout", 15000)); // default: 15 seconds
       
    67     }
       
    68 
       
    69     /** whether to fallback to HTTP on general connect failures */
       
    70     private static final boolean eagerHttpFallback =
       
    71         java.security.AccessController.doPrivileged((PrivilegedAction<Boolean>) () ->
       
    72             Boolean.getBoolean("sun.rmi.transport.proxy.eagerHttpFallback"));
       
    73 
       
    74     /** table of hosts successfully connected to and the factory used */
       
    75     private Hashtable<String, RMISocketFactory> successTable =
       
    76         new Hashtable<>();
       
    77 
       
    78     /** maximum number of hosts to remember successful connection to */
       
    79     private static final int MaxRememberedHosts = 64;
       
    80 
       
    81     /** list of the hosts in successTable in initial connection order */
       
    82     private Vector<String> hostList = new Vector<>(MaxRememberedHosts);
       
    83 
       
    84     /** default factory for initial use for direct socket connection */
       
    85     protected RMISocketFactory initialFactory = new RMIDirectSocketFactory();
       
    86 
       
    87     /** ordered list of factories to try as alternate connection
       
    88       * mechanisms if a direct socket connections fails */
       
    89     protected Vector<RMISocketFactory> altFactoryList;
       
    90 
       
    91     /**
       
    92      * Create a RMIMasterSocketFactory object.  Establish order of
       
    93      * connection mechanisms to attempt on createSocket, if a direct
       
    94      * socket connection fails.
       
    95      */
       
    96     public RMIMasterSocketFactory() {
       
    97         altFactoryList = new Vector<>(2);
       
    98         boolean setFactories = false;
       
    99 
       
   100         try {
       
   101             String proxyHost;
       
   102             proxyHost = java.security.AccessController.doPrivileged(
       
   103                (PrivilegedAction<String>) () -> System.getProperty("http.proxyHost"));
       
   104 
       
   105             if (proxyHost == null)
       
   106                 proxyHost = java.security.AccessController.doPrivileged(
       
   107                     (PrivilegedAction<String>) () -> System.getProperty("proxyHost"));
       
   108 
       
   109             boolean disable = java.security.AccessController.doPrivileged(
       
   110                 (PrivilegedAction<String>) () -> System.getProperty("java.rmi.server.disableHttp", "true"))
       
   111                 .equalsIgnoreCase("true");
       
   112 
       
   113             if (!disable && proxyHost != null && proxyHost.length() > 0) {
       
   114                 setFactories = true;
       
   115             }
       
   116         } catch (Exception e) {
       
   117             // unable to obtain the properties, so use the default behavior.
       
   118         }
       
   119 
       
   120         if (setFactories) {
       
   121             altFactoryList.addElement(new RMIHttpToPortSocketFactory());
       
   122             altFactoryList.addElement(new RMIHttpToCGISocketFactory());
       
   123         }
       
   124     }
       
   125 
       
   126     /**
       
   127      * Create a new client socket.  If we remember connecting to this host
       
   128      * successfully before, then use the same factory again.  Otherwise,
       
   129      * try using a direct socket connection and then the alternate factories
       
   130      * in the order specified in altFactoryList.
       
   131      */
       
   132     public Socket createSocket(String host, int port)
       
   133         throws IOException
       
   134     {
       
   135         if (proxyLog.isLoggable(Log.BRIEF)) {
       
   136             proxyLog.log(Log.BRIEF, "host: " + host + ", port: " + port);
       
   137         }
       
   138 
       
   139         /*
       
   140          * If we don't have any alternate factories to consult, short circuit
       
   141          * the fallback procedure and delegate to the initial factory.
       
   142          */
       
   143         if (altFactoryList.size() == 0) {
       
   144             return initialFactory.createSocket(host, port);
       
   145         }
       
   146 
       
   147         RMISocketFactory factory;
       
   148 
       
   149         /*
       
   150          * If we remember successfully connecting to this host before,
       
   151          * use the same factory.
       
   152          */
       
   153         factory = successTable.get(host);
       
   154         if (factory != null) {
       
   155             if (proxyLog.isLoggable(Log.BRIEF)) {
       
   156                 proxyLog.log(Log.BRIEF,
       
   157                     "previously successful factory found: " + factory);
       
   158             }
       
   159             return factory.createSocket(host, port);
       
   160         }
       
   161 
       
   162         /*
       
   163          * Next, try a direct socket connection.  Open socket in another
       
   164          * thread and only wait for specified timeout, in case the socket
       
   165          * would otherwise spend minutes trying an unreachable host.
       
   166          */
       
   167         Socket initialSocket = null;
       
   168         Socket fallbackSocket = null;
       
   169         final AsyncConnector connector =
       
   170             new AsyncConnector(initialFactory, host, port,
       
   171                 AccessController.getContext());
       
   172                 // connection must be attempted with
       
   173                 // this thread's access control context
       
   174         IOException initialFailure = null;
       
   175 
       
   176         try {
       
   177             synchronized (connector) {
       
   178 
       
   179                 Thread t = java.security.AccessController.doPrivileged(
       
   180                     new NewThreadAction(connector, "AsyncConnector", true));
       
   181                 t.start();
       
   182 
       
   183                 try {
       
   184                     long now = System.currentTimeMillis();
       
   185                     long deadline = now + connectTimeout;
       
   186                     do {
       
   187                         connector.wait(deadline - now);
       
   188                         initialSocket = checkConnector(connector);
       
   189                         if (initialSocket != null)
       
   190                             break;
       
   191                         now = System.currentTimeMillis();
       
   192                     } while (now < deadline);
       
   193                 } catch (InterruptedException e) {
       
   194                     throw new InterruptedIOException(
       
   195                         "interrupted while waiting for connector");
       
   196                 }
       
   197             }
       
   198 
       
   199             // assume no route to host (for now) if no connection yet
       
   200             if (initialSocket == null)
       
   201                 throw new NoRouteToHostException(
       
   202                     "connect timed out: " + host);
       
   203 
       
   204             proxyLog.log(Log.BRIEF, "direct socket connection successful");
       
   205 
       
   206             return initialSocket;
       
   207 
       
   208         } catch (UnknownHostException | NoRouteToHostException e) {
       
   209             initialFailure = e;
       
   210         } catch (SocketException e) {
       
   211             if (eagerHttpFallback) {
       
   212                 initialFailure = e;
       
   213             } else {
       
   214                 throw e;
       
   215             }
       
   216         } finally {
       
   217             if (initialFailure != null) {
       
   218 
       
   219                 if (proxyLog.isLoggable(Log.BRIEF)) {
       
   220                     proxyLog.log(Log.BRIEF,
       
   221                         "direct socket connection failed: ", initialFailure);
       
   222                 }
       
   223 
       
   224                 // Finally, try any alternate connection mechanisms.
       
   225                 for (int i = 0; i < altFactoryList.size(); ++ i) {
       
   226                     factory = altFactoryList.elementAt(i);
       
   227                     if (proxyLog.isLoggable(Log.BRIEF)) {
       
   228                         proxyLog.log(Log.BRIEF,
       
   229                             "trying with factory: " + factory);
       
   230                     }
       
   231                     try (Socket testSocket =
       
   232                             factory.createSocket(host, port)) {
       
   233                         // For HTTP connections, the output (POST request) must
       
   234                         // be sent before we verify a successful connection.
       
   235                         // So, sacrifice a socket for the sake of testing...
       
   236                         // The following sequence should verify a successful
       
   237                         // HTTP connection if no IOException is thrown.
       
   238                         InputStream in = testSocket.getInputStream();
       
   239                         int b = in.read(); // probably -1 for EOF...
       
   240                     } catch (IOException ex) {
       
   241                         if (proxyLog.isLoggable(Log.BRIEF)) {
       
   242                             proxyLog.log(Log.BRIEF, "factory failed: ", ex);
       
   243                         }
       
   244 
       
   245                         continue;
       
   246                     }
       
   247                     proxyLog.log(Log.BRIEF, "factory succeeded");
       
   248 
       
   249                     // factory succeeded, open new socket for caller's use
       
   250                     try {
       
   251                         fallbackSocket = factory.createSocket(host, port);
       
   252                     } catch (IOException ex) {  // if it fails 2nd time,
       
   253                     }                           // just give up
       
   254                     break;
       
   255                 }
       
   256             }
       
   257         }
       
   258 
       
   259         synchronized (successTable) {
       
   260             try {
       
   261                 // check once again to see if direct connection succeeded
       
   262                 synchronized (connector) {
       
   263                     initialSocket = checkConnector(connector);
       
   264                 }
       
   265                 if (initialSocket != null) {
       
   266                     // if we had made another one as well, clean it up...
       
   267                     if (fallbackSocket != null)
       
   268                         fallbackSocket.close();
       
   269                     return initialSocket;
       
   270                 }
       
   271                 // if connector ever does get socket, it won't be used
       
   272                 connector.notUsed();
       
   273             } catch (UnknownHostException | NoRouteToHostException e) {
       
   274                 initialFailure = e;
       
   275             } catch (SocketException e) {
       
   276                 if (eagerHttpFallback) {
       
   277                     initialFailure = e;
       
   278                 } else {
       
   279                     throw e;
       
   280                 }
       
   281             }
       
   282             // if we had found an alternate mechanism, go and use it
       
   283             if (fallbackSocket != null) {
       
   284                 // remember this successful host/factory pair
       
   285                 rememberFactory(host, factory);
       
   286                 return fallbackSocket;
       
   287             }
       
   288             throw initialFailure;
       
   289         }
       
   290     }
       
   291 
       
   292     /**
       
   293      * Remember a successful factory for connecting to host.
       
   294      * Currently, excess hosts are removed from the remembered list
       
   295      * using a Least Recently Created strategy.
       
   296      */
       
   297     void rememberFactory(String host, RMISocketFactory factory) {
       
   298         synchronized (successTable) {
       
   299             while (hostList.size() >= MaxRememberedHosts) {
       
   300                 successTable.remove(hostList.elementAt(0));
       
   301                 hostList.removeElementAt(0);
       
   302             }
       
   303             hostList.addElement(host);
       
   304             successTable.put(host, factory);
       
   305         }
       
   306     }
       
   307 
       
   308     /**
       
   309      * Check if an AsyncConnector succeeded.  If not, return socket
       
   310      * given to fall back to.
       
   311      */
       
   312     Socket checkConnector(AsyncConnector connector)
       
   313         throws IOException
       
   314     {
       
   315         Exception e = connector.getException();
       
   316         if (e != null) {
       
   317             e.fillInStackTrace();
       
   318             /*
       
   319              * The AsyncConnector implementation guaranteed that the exception
       
   320              * will be either an IOException or a RuntimeException, and we can
       
   321              * only throw one of those, so convince that compiler that it must
       
   322              * be one of those.
       
   323              */
       
   324             if (e instanceof IOException) {
       
   325                 throw (IOException) e;
       
   326             } else if (e instanceof RuntimeException) {
       
   327                 throw (RuntimeException) e;
       
   328             } else {
       
   329                 throw new Error("internal error: " +
       
   330                     "unexpected checked exception: " + e.toString());
       
   331             }
       
   332         }
       
   333         return connector.getSocket();
       
   334     }
       
   335 
       
   336     /**
       
   337      * Create a new server socket.
       
   338      */
       
   339     public ServerSocket createServerSocket(int port) throws IOException {
       
   340         //return new HttpAwareServerSocket(port);
       
   341         return initialFactory.createServerSocket(port);
       
   342     }
       
   343 
       
   344 
       
   345     /**
       
   346      * AsyncConnector is used by RMIMasterSocketFactory to attempt socket
       
   347      * connections on a separate thread.  This allows RMIMasterSocketFactory
       
   348      * to control how long it will wait for the connection to succeed.
       
   349      */
       
   350     private class AsyncConnector implements Runnable {
       
   351 
       
   352         /** what factory to use to attempt connection */
       
   353         private RMISocketFactory factory;
       
   354 
       
   355         /** the host to connect to */
       
   356         private String host;
       
   357 
       
   358         /** the port to connect to */
       
   359         private int port;
       
   360 
       
   361         /** access control context to attempt connection within */
       
   362         private AccessControlContext acc;
       
   363 
       
   364         /** exception that occurred during connection, if any */
       
   365         private Exception exception = null;
       
   366 
       
   367         /** the connected socket, if successful */
       
   368         private Socket socket = null;
       
   369 
       
   370         /** socket should be closed after created, if ever */
       
   371         private boolean cleanUp = false;
       
   372 
       
   373         /**
       
   374          * Create a new asynchronous connector object.
       
   375          */
       
   376         AsyncConnector(RMISocketFactory factory, String host, int port,
       
   377                        AccessControlContext acc)
       
   378         {
       
   379             this.factory = factory;
       
   380             this.host    = host;
       
   381             this.port    = port;
       
   382             this.acc     = acc;
       
   383             SecurityManager security = System.getSecurityManager();
       
   384             if (security != null) {
       
   385                 security.checkConnect(host, port);
       
   386             }
       
   387         }
       
   388 
       
   389         /**
       
   390          * Attempt socket connection in separate thread.  If successful,
       
   391          * notify master waiting,
       
   392          */
       
   393         public void run() {
       
   394             try {
       
   395                 /*
       
   396                  * Using the privileges of the thread that wants to make the
       
   397                  * connection is tempting, but it will fail with applets with
       
   398                  * the current applet security manager because the applet
       
   399                  * network connection policy is not captured in the permission
       
   400                  * framework of the access control context we have.
       
   401                  *
       
   402                  * java.security.AccessController.beginPrivileged(acc);
       
   403                  */
       
   404                 try {
       
   405                     Socket temp = factory.createSocket(host, port);
       
   406                     synchronized (this) {
       
   407                         socket = temp;
       
   408                         notify();
       
   409                     }
       
   410                     rememberFactory(host, factory);
       
   411                     synchronized (this) {
       
   412                         if (cleanUp)
       
   413                           try {
       
   414                               socket.close();
       
   415                           } catch (IOException e) {
       
   416                           }
       
   417                     }
       
   418                 } catch (Exception e) {
       
   419                     /*
       
   420                      * Note that the only exceptions which could actually have
       
   421                      * occurred here are IOException or RuntimeException.
       
   422                      */
       
   423                     synchronized (this) {
       
   424                         exception = e;
       
   425                         notify();
       
   426                     }
       
   427                 }
       
   428             } finally {
       
   429                 /*
       
   430                  * See above comments for matching beginPrivileged() call that
       
   431                  * is also commented out.
       
   432                  *
       
   433                  * java.security.AccessController.endPrivileged();
       
   434                  */
       
   435             }
       
   436         }
       
   437 
       
   438         /**
       
   439          * Get exception that occurred during connection attempt, if any.
       
   440          * In the current implementation, this is guaranteed to be either
       
   441          * an IOException or a RuntimeException.
       
   442          */
       
   443         private synchronized Exception getException() {
       
   444             return exception;
       
   445         }
       
   446 
       
   447         /**
       
   448          * Get successful socket, if any.
       
   449          */
       
   450         private synchronized Socket getSocket() {
       
   451             return socket;
       
   452         }
       
   453 
       
   454         /**
       
   455          * Note that this connector's socket, if ever successfully created,
       
   456          * will not be used, so it should be cleaned up quickly
       
   457          */
       
   458         synchronized void notUsed() {
       
   459             if (socket != null) {
       
   460                 try {
       
   461                     socket.close();
       
   462                 } catch (IOException e) {
       
   463                 }
       
   464             }
       
   465             cleanUp = true;
       
   466         }
       
   467     }
       
   468 }