Тест JUnit с сервером Embedded tomcat, как указать автоматические порты как для HTTP, так и для https-коннекторов?

Описание

Я сделал тест JUnit, который фокусируется на попытке протестировать вызов веб-службы SOAP.

Я использую встроенный сервер tomcat для моего теста, чтобы запустить мой тест с помощью макетного сервера.

Я также использую как http, так и https-коннекторы.

Мне нужно использовать автоматические порты для обоих этих разъемов, потому что тест выполняется на сервере Jenkins, и я не могу просто использовать порт 443 или 8443, поскольку они уже сделаны.

Я понимаю, что использование порта 0 в качестве стандартного порта приведет к тому, что tomcat использует автоматическое распределение портов, но я не могу использовать его с обоими коннекторами.

Ожидаемое поведение

Я бы хотел использовать автоматическое распределение портов для моего собственного ssl-коннектора.

Можно ли это сделать каким-то образом?

Пример кода

Вот код для моего экземпляра tomcat:

@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();
  }

и здесь для моего пользовательского ssl-коннектора.

    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;
}

Ответ 1

У меня есть два решения для этой проблемы:

Выберите порт SSL вручную

Конструктор ServerSocket (0) автоматически выбирает свободный порт. Tomcat также использует этот метод.

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();

Я знаю, есть вероятность, что другой процесс выделяет один и тот же порт между testSocket.close() и tomcat.start(), но вы можете обнаружить эту ситуацию с помощью теста LifecycleState.FAILED.equals(sslConnector.getState()).

Использование слушателей жизненного цикла

Разъемы Tomcat - это жизненный цикл, поэтому вы будете уведомлены о событиях "before_init" и "after_init". Tomcat инициализирует соединители в порядке, когда вы добавили их в Сервис.

  • Добавьте соединитель ssl.
  • Добавьте соединитель http. (Это будет соединитель по умолчанию. Не вызывайте mTomcat.getConnector(), потому что он получает первый или создает новый соединитель.)
  • После завершения инициализации разъема ssl вы можете получить выбранный порт с помощью getLocalPort().
  • Перед инициализацией http-соединения вызовите setRedirectPort

Полный пример:

    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();

Ответ 2

Я не пробовал, но из кода он выглядит как

  • Вы можете setRedirectPort после запуска сервера

  • Вы можете использовать Connector.getLocalPort для получения фактического порта

Итак, я думаю, вы могли бы попробовать добавить что-то вроде

mTomcat.start(); // <-- your existing code
defaultConnector.setRedirectPort(SSLConnector.getLocalPort())