java/com/jetbrains/cef/remote/ThriftTransport.java (335 lines of code) (raw):

package com.jetbrains.cef.remote; import com.jetbrains.cef.remote.thrift.transport.*; import org.cef.OS; import org.cef.misc.CefLog; import org.cef.misc.Utils; import java.util.List; import javax.swing.*; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import java.awt.*; import java.io.*; import java.net.*; import java.nio.channels.Channels; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; import java.nio.file.Path; import java.text.SimpleDateFormat; import java.util.Date; import java.util.HashSet; import java.util.Set; public class ThriftTransport { static final boolean MANUAL_SERVER_SELECT = Utils.getBoolean("JCEF_MANUAL_SERVER_SELECT") || Utils.getBoolean("jcef.select.server"); private static final boolean IS_TCP_USED; private static final int PORT_CEF_SERVER; private static final int PORT_JAVA_HANDLERS; private static final String PIPENAME_JAVA_HANDLERS; private static final String PIPENAME_CEF_SERVER; private static final long PID = ProcessHandle.current().pid(); private static final String SUFFIX; private static final Path PIPE_DIR = Path.of(System.getProperty("java.io.tmpdir")); private final String myPipe; private final int myPort; public static final ThriftTransport ourDefaultServer; public static final ThriftTransport ourDefaultClient; static { IS_TCP_USED = !Utils.getBoolean("CEF_SERVER_USE_PIPE"); if (!Utils.getBoolean("DONT_JCEF_USE_UNIQUE_NAMES")) { final SimpleDateFormat f = new SimpleDateFormat("hh_mm_ss_SSS"); SUFFIX = "_" + PID + "_" + f.format(new Date()); } else SUFFIX = "_" + PID; if (IS_TCP_USED) { final int[] customPort = new int[]{Utils.getInteger("ALT_CEF_SERVER_PORT", -1)}; if (MANUAL_SERVER_SELECT) { JTextField textField = new JTextField("", 10); JLabel label = new JLabel("Enter port"); JPanel portComponent = new JPanel(new BorderLayout()); portComponent.add(label,BorderLayout.WEST); portComponent.add(textField,BorderLayout.EAST); JPanel panel = new JPanel(new BorderLayout()); panel.add(portComponent, BorderLayout.SOUTH); List<ProcessLister.RunningServerInfo> runningPorts = ProcessLister.listRunningInstancesPorts(); if (runningPorts != null && !runningPorts.isEmpty()) { // Fill list String[] runningList = new String[runningPorts.size()]; int c = 0; for (ProcessLister.RunningServerInfo s : runningPorts) runningList[c++] = s.transport.myPort + ", parent: " + s.getParentProcessInfo(); JList<String> list = new JList<>(runningList); list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); list.setFont(list.getFont().deriveFont(13f)); list.setVisibleRowCount(8); list.addListSelectionListener(new ListSelectionListener() { @Override public void valueChanged(ListSelectionEvent e) { final String selected = list.getSelectedValue().trim(); customPort[0] = Integer.parseInt(selected.substring(0, selected.indexOf(','))); textField.setText(list.getSelectedValue()); CefLog.Debug("MANUAL_SERVER_SELECT: selected port %d", customPort[0]); } }); JScrollPane scrollPane = new JScrollPane(list); scrollPane.setPreferredSize(new Dimension(400, 250)); panel.add(scrollPane, BorderLayout.CENTER); } JOptionPane.showMessageDialog( null, panel, "Select running cef_server", JOptionPane.PLAIN_MESSAGE ); try { customPort[0] = Integer.parseInt(textField.getText()); } catch (NumberFormatException e) {} } if (customPort[0] == -1) { PORT_CEF_SERVER = findFreePort(null); if (PORT_CEF_SERVER == -1) CefLog.Error("Can't find free tcp-port for server."); else CefLog.Info("Found free tcp-port %d for server.", PORT_CEF_SERVER); } else { CefLog.Info("Use custom tcp-port %d for server.", customPort[0]); PORT_CEF_SERVER = customPort[0]; } customPort[0] = Utils.getInteger("ALT_JAVA_HANDLERS_PORT", -1); if (customPort[0] == -1) { Set<Integer> exclude = new HashSet<>(); exclude.add(PORT_CEF_SERVER); PORT_JAVA_HANDLERS = findFreePort(exclude); if (PORT_JAVA_HANDLERS == -1) CefLog.Error("Can't find free tcp-port for java-handlers."); else CefLog.Info("Found free tcp-port %d for java-handlers.", PORT_JAVA_HANDLERS); } else { CefLog.Info("Use custom tcp-port %d for java-handlers.", customPort[0]); PORT_JAVA_HANDLERS = customPort[0]; } ourDefaultServer = new ThriftTransport(getServerPort()); ourDefaultClient = new ThriftTransport(getJavaHandlersPort()); PIPENAME_JAVA_HANDLERS = ""; PIPENAME_CEF_SERVER = ""; } else { PORT_CEF_SERVER = 0; PORT_JAVA_HANDLERS = 0; final String pipeServerDefault = "cef_server_pipe"; final String pipeServerCustom = Utils.getString("ALT_CEF_SERVER_PIPE"); final String suffixServer; if (pipeServerCustom == null || pipeServerCustom.isEmpty()) { PIPENAME_CEF_SERVER = pipeServerDefault; suffixServer = SUFFIX; } else { PIPENAME_CEF_SERVER = pipeServerCustom; suffixServer = ""; } String pipeJavaDefault = "client_pipe"; String pipeJavaCustom = Utils.getString("ALT_JAVA_HANDLERS_PIPE"); final String suffixJava; if (pipeJavaCustom == null || pipeJavaCustom.isEmpty()) { PIPENAME_JAVA_HANDLERS = pipeJavaDefault; suffixJava = SUFFIX; } else { PIPENAME_JAVA_HANDLERS = pipeJavaCustom; suffixJava = ""; } String pipe; if (OS.isWindows()) pipe = PIPENAME_CEF_SERVER + suffixServer; else { pipe = PIPE_DIR.resolve(PIPENAME_CEF_SERVER + suffixServer).toString(); pipe = normalizePipeName(pipe); } ourDefaultServer = new ThriftTransport(pipe); if (OS.isWindows()) pipe = PIPENAME_JAVA_HANDLERS + suffixJava; else { pipe = PIPE_DIR.resolve(PIPENAME_JAVA_HANDLERS + suffixJava).toString(); pipe = normalizePipeName(pipe); } ourDefaultClient = new ThriftTransport(pipe); } } public ThriftTransport(File pipe) { this.myPipe = OS.isWindows() ? pipe.getName() : pipe.getAbsolutePath() ; this.myPort = 0; } public ThriftTransport(String pipe) { this.myPipe = pipe; this.myPort = 0; } public ThriftTransport(int port) { this.myPipe = null; this.myPort = port; } public boolean isTcp() { return myPipe == null; } public String getPipe() { return myPipe; } public int getPort() { return myPort; } @Override public String toString() { return myPipe != null ? String.format("pipe='%s'", myPipe) : String.format("port=%d", myPort); } public String toStringShort() { return myPipe != null ? String.format("pipe_%s", myPipe).trim().replace(" ","") : String.format("port_%d", myPort); } @Override public boolean equals(Object obj) { if (obj instanceof ThriftTransport) { ThriftTransport other = (ThriftTransport)obj; return myPipe != null ? myPipe.equals(other.myPipe) : myPort == other.myPort; } return false; } public static String getJavaHandlersPipe(String suffix) { if (OS.isWindows()) return PIPENAME_JAVA_HANDLERS + "_" + suffix; return PIPE_DIR.resolve(PIPENAME_JAVA_HANDLERS + "_" + suffix).toString(); } public static String getServerPipe(String suffix) { if (OS.isWindows()) return PIPENAME_CEF_SERVER + "_" + suffix; return PIPE_DIR.resolve(PIPENAME_CEF_SERVER + "_" + suffix).toString(); } public static boolean isTcpUsed() { return IS_TCP_USED; } public static String getUniqueSuffix() { return SUFFIX; } private static int getServerPort() { return PORT_CEF_SERVER; } private static int getJavaHandlersPort() { return PORT_JAVA_HANDLERS; } public static int findFreePort() { return findFreePort(null); } public static int findFreePort(Set<Integer> exclude) { return findFreePort(9999, 65500, exclude); } public static int findFreePort(int from, int to, Set<Integer> exclude) { for (int port = from; port < to; ++port) { if (exclude != null && exclude.contains(port)) continue; try { ServerSocket ss = new ServerSocket(port, 0, InetAddress.getByName(null)); ss.close(); return port; } catch (IOException e) {} } return -1; } public TServerTransport createServerTransport() throws Exception { if (isTcp()) return new TServerSocket(new InetSocketAddress(InetAddress.getByName(null), myPort)); if (OS.isWindows()) { WindowsPipeServerSocket pipeSocket = new WindowsPipeServerSocket(myPipe); return new TServerTransport() { @Override public void listen() {} @Override public TTransport accept() throws TTransportException { try { Socket client = pipeSocket.accept(); return client != null ? new TIOStreamTransport(client.getInputStream(), client.getOutputStream()) : null; } catch (IOException e) { CefLog.Debug("Exception occurred during pipe listening: %s", e); throw new TTransportException(TTransportException.UNKNOWN, e.getMessage()); } } @Override public void close() { try { pipeSocket.close(); } catch (IOException e) { CefLog.Error("Exception occurred during pipe closing: %s", e); } } }; } // Linux or OSX new File(myPipe).delete(); // cleanup file remaining from prev process ServerSocketChannel serverChannel = ServerSocketChannel.open(StandardProtocolFamily.UNIX); serverChannel.bind(UnixDomainSocketAddress.of(myPipe)); return new TServerTransport() { @Override public void listen() {} @Override public TTransport accept() throws TTransportException { try { SocketChannel channel = serverChannel.accept(); InputStream is = new BufferedInputStream(Channels.newInputStream(channel)); OutputStream os = new BufferedOutputStream(Channels.newOutputStream(channel)); return new TIOStreamTransport(is, os); } catch (IOException e) { CefLog.Debug("Exception occurred during pipe listening: %s", e); throw new TTransportException(TTransportException.UNKNOWN, e.getMessage()); } } @Override public void close() { try { serverChannel.close(); } catch (IOException e) { CefLog.Error("Exception occurred during pipe closing: %s", e); } try { new File(myPipe).delete(); } catch (RuntimeException e) { CefLog.Error("RuntimeException occurred when trying to delete file of pipe '%s', error: %s", myPipe, e); } } }; } public TIOStreamTransport openPipeTransport() throws TTransportException { try { InputStream is; OutputStream os; final Runnable closer; if (OS.isWindows()) { WindowsPipeSocket pipe = new WindowsPipeSocket(myPipe); is = pipe.getInputStream(); os = pipe.getOutputStream(); closer = ()->{ try { pipe.close(); } catch (IOException e) {} }; } else { SocketChannel channel = SocketChannel.open(StandardProtocolFamily.UNIX); UnixDomainSocketAddress socketAddress = UnixDomainSocketAddress.of(myPipe); channel.connect(socketAddress); is = Channels.newInputStream(channel); os = Channels.newOutputStream(channel); closer = ()->{ try { channel.close(); } catch (IOException e) {} }; } return new TIOStreamTransport(is, os) { @Override public void close() { closer.run(); } }; } catch (IOException e) { throw new TTransportException(e.getMessage()); } } public static File[] findPipes() { if (OS.isWindows()) { String[] pipes = WindowsPipe.findPipes(PIPENAME_CEF_SERVER + "*"); if (pipes == null || pipes.length == 0) return null; File[] result = new File[pipes.length]; for (int i = 0; i < pipes.length; i++) result[i] = new File(pipes[i]); return result; } return PIPE_DIR.toFile().listFiles((dir, name) -> name.startsWith(PIPENAME_CEF_SERVER)); } private static String normalizePipeName(String pipeName) { if (OS.isWindows()) return pipeName; // https://unix.stackexchange.com/questions/367008/why-is-socket-path-length-limited-to-a-hundred-chars if (pipeName == null || pipeName.isEmpty()) return null; if (pipeName.length() < 100) return pipeName; final SimpleDateFormat f = new SimpleDateFormat("mm_ss_SSS"); final String newShortName = "cc_" + f.format(new Date()); String newName = Path.of(System.getProperty("java.io.tmpdir")).resolve(newShortName).toString(); if (newName.length() < 100) return newName; return "/var/tmp/" + newShortName; } }