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 } |
|