Skip to content

Commit

Permalink
chore: pass watchdog ip address to dev server process (#13506)
Browse files Browse the repository at this point in the history
* chore: pass watchdog ip address to dev server process

Dev server monitors watchdog port on localhost to be sure that Java process
is running, otherwise it kills itself.
It could happend that Java and Node processes resolve localhost ip
address differently (ipv4 vs ipv6), for example when using Node 17 and
setting Java system property `-Djava.net.preferIPv4Stack=true`.
This change resolves the ip address in Java process and provides it to
the node process so it will bind to the correct address.
  • Loading branch information
mcollovati committed Apr 14, 2022
1 parent 12db9fe commit 8878410
Show file tree
Hide file tree
Showing 4 changed files with 119 additions and 6 deletions.
8 changes: 4 additions & 4 deletions flow-server/src/main/resources/vite.generated.ts
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,7 @@ function updateTheme(contextPath: string) {
}
}

function runWatchDog(watchDogPort) {
function runWatchDog(watchDogPort, watchDogHost) {
const client = net.Socket();
client.setEncoding('utf8');
client.on('error', function (err) {
Expand All @@ -299,10 +299,10 @@ function runWatchDog(watchDogPort) {
});
client.on('close', function () {
client.destroy();
runWatchDog(watchDogPort);
runWatchDog(watchDogPort, watchDogHost);
});

client.connect(watchDogPort, 'localhost');
client.connect(watchDogPort, watchDogHost || 'localhost');
}

let spaMiddlewareForceRemoved = false;
Expand All @@ -320,7 +320,7 @@ export const vaadinConfig: UserConfigFn = (env) => {
if (devMode && process.env.watchDogPort) {
// Open a connection with the Java dev-mode handler in order to finish
// vite when it exits or crashes.
runWatchDog(process.env.watchDogPort);
runWatchDog(process.env.watchDogPort, process.env.watchDogHost);
}

return {
Expand Down
4 changes: 2 additions & 2 deletions flow-server/src/main/resources/webpack.generated.js
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ let stats;
// Open a connection with the Java dev-mode handler in order to finish
// webpack-dev-mode when it exits or crashes.
const watchDogPort = devMode && process.env.watchDogPort;
const watchDogHost = (devMode && process.env.watchDogHost) || 'localhost';
if (watchDogPort) {
const runWatchDog = () => {
const client = new require('net').Socket();
Expand All @@ -105,8 +106,7 @@ if (watchDogPort) {
client.destroy();
runWatchDog();
});

client.connect(watchDogPort, 'localhost');
client.connect(watchDogPort, watchDogHost);
};
runWatchDog();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,10 @@
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.net.HttpURLConnection;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.URL;
import java.net.UnknownHostException;
import java.nio.charset.StandardCharsets;
import java.util.Enumeration;
import java.util.List;
Expand Down Expand Up @@ -315,10 +317,16 @@ protected abstract List<String> getServerStartupCommand(
*/
protected void updateServerStartupEnvironment(FrontendTools frontendTools,
Map<String, String> environment) {
environment.put("watchDogHost", getLoopbackAddress().getHostAddress());
environment.put("watchDogPort",
Integer.toString(getWatchDog().getWatchDogPort()));
}

// visible for tests
InetAddress getLoopbackAddress() {
return InetAddress.getLoopbackAddress();
}

/**
* Gets a pattern to match with the output to determine that the server has
* started successfully.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,20 @@
import java.io.File;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.regex.Pattern;

import javax.servlet.ServletOutputStream;
Expand All @@ -16,9 +25,12 @@

import com.vaadin.base.devserver.startup.AbstractDevModeTest;
import com.vaadin.flow.internal.DevModeHandler;
import com.vaadin.flow.internal.ReflectTools;
import com.vaadin.flow.server.frontend.FrontendTools;
import com.vaadin.flow.server.frontend.FrontendToolsSettings;

import org.junit.Assert;
import org.junit.AssumptionViolatedException;
import org.junit.Test;
import org.mockito.Mockito;

Expand Down Expand Up @@ -74,6 +86,12 @@ public HttpURLConnection prepareConnection(String path, String method)
return Mockito.mock(HttpURLConnection.class);
}

// Expose for testing
@Override
public void updateServerStartupEnvironment(FrontendTools frontendTools,
Map<String, String> environment) {
super.updateServerStartupEnvironment(frontendTools, environment);
}
}

@Test
Expand All @@ -100,4 +118,91 @@ public void shouldPassEncodedUrlToDevServer() throws Exception {
Assert.assertEquals("foo%20bar", requestedPath.get());

}

@Test
public void updateServerStartupEnvironment_preferIpv4_LocalhostIpAddressAddedToProcessEnvironment() {
assertOnDevProcessEnvironment(Inet4Address.class, environment -> {
Assert.assertNotNull(
"Expecting watchDogPort to be added to environment, but was not",
environment.get("watchDogPort"));

String watchDogHost = environment.get("watchDogHost");
Assert.assertNotNull(
"Expecting watchDogHost to be added to environment, but was not",
watchDogHost);
// From InetAddress javadocs:
// The IPv4 loopback address returned is only one of many in the
// form 127.*.*.*
Assert.assertTrue(
"Expecting watchDogHost to be an ipv4 address, but was "
+ watchDogHost,
watchDogHost.matches("127\\.\\d+\\.\\d+\\.\\d+"));
});
}

@Test
public void updateServerStartupEnvironment_preferIpv6_LocalhostIpAddressAddedToProcessEnvironment() {
assertOnDevProcessEnvironment(Inet6Address.class, environment -> {
Assert.assertNotNull(
"Expecting watchDogPort to be added to environment, but was not",
environment.get("watchDogPort"));

String watchDogHost = environment.get("watchDogHost");
Assert.assertNotNull(
"Expecting watchDogHost to be added to environment, but was not",
watchDogHost);
Assert.assertTrue(
"Expecting watchDogHost to be an ipv6 address, but was "
+ watchDogHost,
"0:0:0:0:0:0:0:1".equals(watchDogHost)
|| "::1".equals(watchDogHost));
});
}

private InetAddress findLocalhostAddress(
Class<? extends InetAddress> type) {
try {
return Arrays.stream(InetAddress.getAllByName("localhost"))
.filter(type::isInstance).findFirst()
.orElseThrow(() -> new AssumptionViolatedException(
"localhost address not found for "
+ type.getName()));
} catch (UnknownHostException e) {
// should never happen for localhost
throw new AssertionError("Cannot detect addresses for localhost",
e);
}
}

private void assertOnDevProcessEnvironment(
Class<? extends InetAddress> loopbackAddressType,
Consumer<Map<String, String>> op) {
final DevServerWatchDog watchDog = new DevServerWatchDog();
final InetAddress loopbackAddress = findLocalhostAddress(
loopbackAddressType);
try {
handler = new DummyRunner() {
@Override
protected DevServerWatchDog getWatchDog() {
return watchDog;
}

@Override
InetAddress getLoopbackAddress() {
return loopbackAddress;
}
};

FrontendTools frontendTools = new FrontendTools(
new FrontendToolsSettings(
System.getProperty("java.io.tmpdir"), null));
Map<String, String> environment = new HashMap<>();
((AbstractDevServerRunner) handler)
.updateServerStartupEnvironment(frontendTools, environment);
op.accept(environment);
} finally {
watchDog.stop();
}
}

}

0 comments on commit 8878410

Please sign in to comment.