package org.jetbrains.ide; import com.intellij.idea.StartupUtil; import com.intellij.notification.NotificationDisplayType; import com.intellij.notification.NotificationGroup; import com.intellij.notification.NotificationType; import com.intellij.openapi.Disposable; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.ApplicationNamesInfo; import com.intellij.openapi.components.ApplicationComponent; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.util.Disposer; import com.intellij.openapi.util.NotNullLazyValue; import com.intellij.openapi.util.text.StringUtil; import com.intellij.util.Url; import com.intellij.util.UrlImpl; import com.intellij.util.net.NetUtils; import io.netty.channel.oio.OioEventLoopGroup; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.builtInWebServer.BuiltInServerOptions; import org.jetbrains.builtInWebServer.BuiltInWebServerKt; import org.jetbrains.io.BuiltInServer; import org.jetbrains.io.SubServer; import java.io.IOException; import java.net.InetAddress; import java.net.NetworkInterface; import java.net.URLConnection; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.concurrent.atomic.AtomicBoolean; public class BuiltInServerManagerImpl extends BuiltInServerManager implements ApplicationComponent { private static final Logger LOG = Logger.getInstance(BuiltInServerManager.class); public static final NotNullLazyValue<NotificationGroup> NOTIFICATION_GROUP = new NotNullLazyValue<NotificationGroup>() { @NotNull @Override protected NotificationGroup compute() { return new NotificationGroup("Built-in Server", NotificationDisplayType.STICKY_BALLOON, true); } }; @NonNls public static final String PROPERTY_RPC_PORT = "rpc.port"; private static final int PORTS_COUNT = 20; private final AtomicBoolean started = new AtomicBoolean(false); @Nullable private BuiltInServer server; @Override public int getPort() { return server == null ? getDefaultPort() : server.getPort(); } @Override public BuiltInServerManager waitForStart() { Future<?> serverStartFuture = startServerInPooledThread(); if (serverStartFuture != null) { LOG.assertTrue(ApplicationManager.getApplication().isUnitTestMode() || !ApplicationManager.getApplication().isDispatchThread()); try { serverStartFuture.get(); } catch (InterruptedException | ExecutionException ignored) { } } return this; } private static int getDefaultPort() { if (System.getProperty(PROPERTY_RPC_PORT) == null) { // Default port will be occupied by main idea instance - define the custom default to avoid searching of free port return ApplicationManager.getApplication().isUnitTestMode() ? 64463 : 63342; } else { return Integer.parseInt(System.getProperty(PROPERTY_RPC_PORT)); } } @Override public void initComponent() { startServerInPooledThread(); } private Future<?> startServerInPooledThread() { if (!started.compareAndSet(false, true)) { return null; } return ApplicationManager.getApplication().executeOnPooledThread(() -> { try { BuiltInServer mainServer = StartupUtil.getServer(); if (mainServer == null || mainServer.getEventLoopGroup() instanceof OioEventLoopGroup) { server = BuiltInServer.start(1, getDefaultPort(), PORTS_COUNT, false, null); } else { server = BuiltInServer.start(mainServer.getEventLoopGroup(), false, getDefaultPort(), PORTS_COUNT, true, null); } bindCustomPorts(server); } catch (Throwable e) { LOG.info(e); NOTIFICATION_GROUP.getValue().createNotification("Cannot start internal HTTP server. Git integration, JavaScript debugger and LiveEdit may operate with errors. " + "Please check your firewall settings and restart " + ApplicationNamesInfo.getInstance().getFullProductName(), NotificationType.ERROR).notify(null); return; } LOG.info("built-in server started, port " + server.getPort()); Disposer.register(ApplicationManager.getApplication(), server); }); } @Override @Nullable public Disposable getServerDisposable() { return server; } @Override public boolean isOnBuiltInWebServer(@Nullable Url url) { return url != null && !StringUtil.isEmpty(url.getAuthority()) && isOnBuiltInWebServerByAuthority(url.getAuthority()); } @Override public Url addAuthToken(@NotNull Url url) { if (url.getParameters() != null) { // built-in server url contains query only if token specified return url; } return new UrlImpl(url.getScheme(), url.getAuthority(), url.getPath(), "?" + BuiltInWebServerKt.TOKEN_PARAM_NAME + "=" + BuiltInWebServerKt.acquireToken()); } @Override public void configureRequestToWebServer(@NotNull URLConnection connection) { connection.setRequestProperty(BuiltInWebServerKt.TOKEN_HEADER_NAME, BuiltInWebServerKt.acquireToken()); } private static void bindCustomPorts(@NotNull BuiltInServer server) { if (ApplicationManager.getApplication().isUnitTestMode()) { return; } for (CustomPortServerManager customPortServerManager : CustomPortServerManager.EP_NAME.getExtensions()) { try { new SubServer(customPortServerManager, server).bind(customPortServerManager.getPort()); } catch (Throwable e) { LOG.error(e); } } } public static boolean isOnBuiltInWebServerByAuthority(@NotNull String authority) { int portIndex = authority.indexOf(':'); if (portIndex < 0 || portIndex == authority.length() - 1) { return false; } int port = StringUtil.parseInt(authority.substring(portIndex + 1), -1); if (port == -1) { return false; } BuiltInServerOptions options = BuiltInServerOptions.getInstance(); int idePort = BuiltInServerManager.getInstance().getPort(); if (options.builtInServerPort != port && idePort != port) { return false; } String host = authority.substring(0, portIndex); if (NetUtils.isLocalhost(host)) { return true; } try { InetAddress inetAddress = InetAddress.getByName(host); return inetAddress.isLoopbackAddress() || inetAddress.isAnyLocalAddress() || (options.builtInServerAvailableExternally && idePort != port && NetworkInterface.getByInetAddress(inetAddress) != null); } catch (IOException e) { return false; } } }