BeepDog
BeepDog

Reputation: 5085

How to get the automatically defined port for a Spark Java Application?

In the API documentation for Java Spark (not Apache spark), you can specify a port of 0 to have it automatically select a port. Great!

However, I cannot figure out how to get that port after the server is started. I can see it in the logs:

15:41:12.459 [Thread-2] INFO  spark.webserver.JettySparkServer - >> Listening on 0.0.0.0:63134

But I need to be able to get to it programmatically, so that my integration tests are able to run reliably every time.

So how do I get that port?

Upvotes: 3

Views: 2066

Answers (2)

Gaëtan Sheridan
Gaëtan Sheridan

Reputation: 165

This will work on Spark 2.6.0:

public static int start (String keystoreFile, String keystorePw)
{
    secure(keystoreFile, keystorePw, null, null);
    port(0);

    staticFiles.location("/public");

    get(Path.CLOCK, ClockController.time);
    get(Path.CALENDAR, CalendarController.date);

    // This is the important line. It must be *after* creating the routes and *before* the call to port()
    awaitInitialization();

    return port();
}

Without the call to awaitInitialization() port() would return 0.

Upvotes: 2

BeepDog
BeepDog

Reputation: 5085

I could find no way to get this information in the API, and so I filed an issue on their github.

I was able to get at it via an ugly pile of reflection:

/**
 * Meant to be called from a different thread, once the spark app is running
 * This is probably only going to be used during the integration testing process, not ever in prod!
 *
 * @return the port it's running on
 */
public static int awaitRunningPort() throws Exception {
    awaitInitialization();
    //I have to get the port via reflection, which is fugly, but the API doesn't exist :(
    //Since we'll only use this in testing, it's not going to kill us
    Object instance = getInstance();
    Class theClass = instance.getClass();
    Field serverField = theClass.getDeclaredField("server");
    serverField.setAccessible(true);
    Object oneLevelDeepServer = serverField.get(instance);

    Class jettyServerClass = oneLevelDeepServer.getClass();
    Field jettyServerField = jettyServerClass.getDeclaredField("server");
    jettyServerField.setAccessible(true);
    //Have to pull in the jetty server stuff to do this mess
    Server jettyServer = (Server)jettyServerField.get(oneLevelDeepServer);

    int acquiredPort = ((ServerConnector)jettyServer.getConnectors()[0]).getLocalPort();

    log.debug("Acquired port: {}", acquiredPort);
    return acquiredPort;
}

This works well for me in our integration tests, but I'm not using https, and it does reach about two levels deep into the API via reflection grabbing protected fields. I could not find any other way to do it. Would be quite happy to be proven wrong.

Upvotes: 3

Related Questions