ServerManager.java
/*
* Copyright (c) 2021 Payara Foundation and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
package org.glassfish.jersey.grizzly2.httpserver.test.tools;
import java.io.Closeable;
import java.io.IOException;
import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.ServerSocket;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.UnknownHostException;
import org.glassfish.grizzly.http.server.HttpServer;
import org.glassfish.grizzly.http.server.NetworkListener;
import org.glassfish.grizzly.http2.Http2AddOn;
import org.glassfish.grizzly.http2.Http2Configuration;
import org.glassfish.grizzly.ssl.SSLContextConfigurator;
import org.glassfish.grizzly.ssl.SSLEngineConfigurator;
import org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory;
import org.glassfish.jersey.grizzly2.httpserver.test.application.TestedEndpoint;
import org.glassfish.jersey.server.ResourceConfig;
/**
* This manager maintains the lifecycle of the {@link HttpServer} and trivial rest application.
*
* @author David Matejcek
*/
public class ServerManager implements Closeable {
private static final String LISTENER_NAME_GRIZZLY = "grizzly";
private static final String PROTOCOL_HTTPS = "https";
private static final String PROTOCOL_HTTP = "http";
private static final String APPLICATION_CONTEXT = "/test-application";
private static final String SERVICE_CONTEXT = "/tested-endpoint";
private final URI endpointUri;
private final HttpServer server;
/**
* Initializes the server environment and starts the server.
*
* @param secured - set true to enable network encryption
* @param useHttp2 - set true to enable HTTP/2; then secured must be set to true too.
* @throws IOException
*/
public ServerManager(final boolean secured, final boolean useHttp2) throws IOException {
this.endpointUri = getEndpointUri(secured, APPLICATION_CONTEXT);
final NetworkListener listener = createListener(secured, useHttp2, endpointUri.getHost(), endpointUri.getPort());
final ResourceConfig resourceConfig = createResourceConfig();
this.server = startServer(listener, this.endpointUri, resourceConfig);
}
/**
* @return {@link URI} of the application endpoint.
*/
public URI getApplicationEndpoint() {
return this.endpointUri;
}
/**
* @return {@link URI} of the deployed service endpoint.
*/
public URI getApplicationServiceEndpoint() {
return URI.create(getApplicationEndpoint() + SERVICE_CONTEXT);
}
/**
* Calls the {@link HttpServer#shutdownNow()}. The server and all it's resources are destroyed.
*/
@Override
public void close() {
if (server != null) {
server.shutdownNow();
}
}
private static URI getEndpointUri(final boolean secured, final String applicationContext) {
try {
final String protocol = secured ? PROTOCOL_HTTPS : PROTOCOL_HTTP;
return new URL(protocol, getLocalhost(), getFreePort(), applicationContext).toURI();
} catch (MalformedURLException | URISyntaxException e) {
throw new IllegalStateException("Unable to create an endpoint URI.", e);
}
}
private static String getLocalhost() {
try {
return InetAddress.getLocalHost().getHostName();
} catch (final UnknownHostException e) {
return "localhost";
}
}
/**
* Tries to alocate a free local port.
*
* @return a free local port number.
* @throws IllegalStateException if it fails for 20 times
*/
private static int getFreePort() {
int attempts = 0;
while (true) {
attempts++;
try (ServerSocket socket = new ServerSocket(0)) {
final int port = socket.getLocalPort();
socket.setSoTimeout(1);
socket.setReuseAddress(true);
return port;
} catch (final IOException e) {
if (attempts >= 20) {
throw new IllegalStateException("Cannot open random port, tried 20 times.", e);
}
}
}
}
private static NetworkListener createListener(final boolean secured, final boolean useHttp2, final String host,
final int port) {
if (useHttp2 && !secured) {
throw new IllegalArgumentException("HTTP/2 cannot be used without encryption");
}
final NetworkListener listener = new NetworkListener(LISTENER_NAME_GRIZZLY, host, port);
listener.setSecure(secured);
if (secured) {
listener.setSSLEngineConfig(createSSLEngineConfigurator(host));
}
if (useHttp2) {
listener.registerAddOn(createHttp2AddOn());
}
return listener;
}
private static SSLEngineConfigurator createSSLEngineConfigurator(final String host) {
final KeyStoreManager keyStoreManager = new KeyStoreManager(host);
final SSLContextConfigurator configurator = new SSLContextConfigurator();
configurator.setKeyStoreBytes(keyStoreManager.getKeyStoreBytes());
configurator.setKeyStorePass(keyStoreManager.getKeyStorePassword());
configurator.setTrustStoreBytes(keyStoreManager.getKeyStoreBytes());
configurator.setTrustStorePass(keyStoreManager.getKeyStorePassword());
final SSLEngineConfigurator sslEngineConfigurator = new SSLEngineConfigurator(configurator)
.setClientMode(false).setNeedClientAuth(false);
return sslEngineConfigurator;
}
private static Http2AddOn createHttp2AddOn() {
final Http2Configuration configuration = Http2Configuration.builder().build();
return new Http2AddOn(configuration);
}
private static ResourceConfig createResourceConfig() {
return new ResourceConfig().registerClasses(TestedEndpoint.class);
}
private static HttpServer startServer(final NetworkListener listener, final URI endpointUri,
final ResourceConfig resourceConfig) {
final HttpServer srv = GrizzlyHttpServerFactory.createHttpServer(endpointUri, resourceConfig, false);
try {
srv.addListener(listener);
srv.start();
return srv;
} catch (final IOException e) {
throw new IllegalStateException("Could not start the server!", e);
}
}
}