Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JUnit test with Embedded tomcat server , how to specify automatic ports for both http and https connectors?

Description

I have made a JUnit test that focus on trying to test a call to a SOAP web service.

I am using an embedded tomcat server for my test in order to run my test with a mock server.

I am also using both http and https connectors.

I need to use automatic ports for both these connectors because the test is running on a Jenkins server and i can't just use port 443 or 8443 as they are already taken.

I understand that using the port 0 as standard port will result in tomcat using automatic port allocation but I can't manage to use it with both connectors.

Expected behavior

I'd like to use automatic port allocation also for my custom ssl connector.

Is it possible to do so in some way ?

Sample code

Here is the code for my tomcat instance :

@Before
public void setup() throws Throwable {

    File tomcatWorkingDir = new File(mWorkingDir);

    //Empty the target/tomcat-working-dir directory if it exist
    //Create the directory otherwise
    if(tomcatWorkingDir.exists() && tomcatWorkingDir.isDirectory()){
        LOGGER.info("cleaning tomcat-working-dir directory");
        FileUtils.cleanDirectory(new File(mWorkingDir)); 
    } else {
        LOGGER.info("create tomcat-working-dir directory");
        tomcatWorkingDir.mkdir();
    }

    LOGGER.info("disabling ssl certification validation");
    //Disable JVM ssl sockets connection
    disableJVMCertificate();

    //Add server certificate
    createServerCertificate();

    //Custom SSL Connector
    Connector SSLConnector = getSSLConnector();

    mTomcat = new Tomcat();

    //Standard http startup port
    mTomcat.setPort(0);

    //Set up base directory 
    //Otherwise, tomcat would use the current directory
    mTomcat.setBaseDir(mWorkingDir);

    LOGGER.info("setting the ssl connector in TOMCAT");
    Service service = mTomcat.getService();
    service.addConnector(SSLConnector);

    //Redirect current port
    Connector defaultConnector = mTomcat.getConnector();
    defaultConnector.setRedirectPort(SERVER_HTTPS_PORT);

    //Configure the way WAR are managed by the engine
    mTomcat.getHost().setAutoDeploy(true);
    mTomcat.getHost().setDeployOnStartup(true);

    //Add mock server into our webApp
    String servletName = "/server";
    File webApp = new File(mWorkingDir,"../../../ws-mock-server/src/main/webapp");

    mTomcat.addWebapp(mTomcat.getHost(), servletName, webApp.getAbsolutePath());

    //start tomcat
    LOGGER.info("starting TOMCAT");

    mTomcat.start();
  }

and here for my custom ssl connector.

    private static Connector getSSLConnector(){
    Connector connector = new Connector();
    connector.setPort(SERVER_HTTPS_PORT);
    connector.setSecure(true);

    //Http protocol Http11AprProtocol
    connector.setAttribute("protocol", "org.apache.coyote.http11.Http11AprProtocol");

    //Maximum threads allowedd on this instance of tomcat
    connector.setAttribute("maxThreads","200");
    connector.setAttribute("SSLEnabled", true);

    //No client Authentification is required in order to connect
    connector.setAttribute("clientAuth", false);

    //SSL TLSv1 protocol
    connector.setAttribute("sslProtocol","TLS");

    //Ciphers configuration describing how server will encrypt his messages
    //A common cipher suite need to exist between server and client in an ssl
    //communication in order for the handshake to succeed
    connector.setAttribute("ciphers","TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA");

    LOGGER.info("setting keystore file");
    //Here an absolute file path is needed in order to properly set up the keystore attribute
    connector.setAttribute("keystoreFile",new File(".").getAbsolutePath().replace("\\", "/")+"/"+mWorkingDir+"/server.jks");

    LOGGER.info("setting keystore pass");
    connector.setAttribute("keystorePass","changeit");

    return connector;
}
like image 732
Florent M Avatar asked Feb 17 '17 13:02

Florent M


People also ask

How to start and stop the Tomcat server while testing?

Create some pre-integration and post-integration hooks that starts and stops the server for you while testing. You can easily do this with Maven or Gradle (check Starting of the Apache tomcat server before integration test)

Is it possible to test the connection in Java 7?

It appears that as of Java 7, David Santamaria's answer doesn't work reliably any more. It looks like you can still reliably use a Socket to test the connection, however.

Can it detect services that open a port on all interfaces?

Of course it can only detect services that open a port on all interfaces or that explicitly run on localhost. Show activity on this post.


2 Answers

I have two solutions for this problem:

Select SSL port manually

The ServerSocket(0) constructor automatically selects a free port. The Tomcat uses this method also.

try (ServerSocket testSocket = new ServerSocket(0)) {
    int randomFreePort = testSocket.getLocalPort(); 
    sslConnector.setPort(randomFreePort);
    defaultConnector.setRedirectPort( randomFreePort);
} // At this point the testSocket.close() called
tomcat.start();

I know, there is a probability, that an another process allocates the same port between the testSocket.close() and tomcat.start(), but you can detect this situation, with LifecycleState.FAILED.equals(sslConnector.getState()) test.

Use lifecycle listeners

Tomcat connectors are lifecycle aware, so you will be notified on 'before_init' and 'after_init' events. Tomcat initializes the connectors in the order as you added them to the Service.

  1. Add the ssl connector.
  2. Add an http connector. (That will be the 'default' connector. Don't call the mTomcat.getConnector() because it gets the first or creates a new connector. )
  3. When the ssl connector initialization complete, you can get the chosen port with getLocalPort() call.
  4. Before the http connector initialization, call the setRedirectPort

Full example:

    Tomcat mTomcat = new Tomcat();
    Connector sslConnector = getSSLConnector(); 
    mTomcat.getService().addConnector(sslConnector);    
    Connector defaultConnector = new Connector();
    defaultConnector.setPort(0);
    mTomcat.getService().addConnector(defaultConnector);

    // Do the rest of the Tomcat setup

    AtomicInteger sslPort = new AtomicInteger();
    sslConnector.addLifecycleListener(event->{
        if( "after_init".equals(event.getType()) )
            sslPort.set(sslConnector.getLocalPort());
    });
    defaultConnector.addLifecycleListener(event->{
        if( "before_init".equals(event.getType()) )
            defaultConnector.setRedirectPort(sslPort.get());
    });

    mTomcat.start();
like image 74
Bukodi László Avatar answered Oct 10 '22 02:10

Bukodi László


I haven't tried it but from the code it looks like

  1. You can setRedirectPort after server was started

  2. You can use Connector.getLocalPort to get actual port

So I think you could try to add something like

mTomcat.start(); // <-- your existing code
defaultConnector.setRedirectPort(SSLConnector.getLocalPort())
like image 24
SergGr Avatar answered Oct 10 '22 04:10

SergGr