“Auf welchem Port läuft der HTTPS-Connector der laufenden Tomcat?” ist eine wichtige Frage, wenn man z. B. einen HTTP -> HTTPS redirect machen möchte und die man mit Tomcat Portdetection beantworten kann.
Als laufende Webanwendung gibt es nur einen sinnvollen Weg, auf die Laufzeitinformationen der Tomcat zuzugreifen, nämlich via MXBeans.
Die MXBeans bieten aber sehr viele Informationen und es ist nicht immer einfach, die Richtigen herauszufischen. So liefert ein ungefilterter Aufruf auf die Informationen der Tomcat mehrere dutzend MBeans über verschiedenste Laufzeitinformationen. Für die Ports sind die Connectoren relevant, die üblicherweise in der server.xml konfiguriert werden. Ein Beispiel für die Informationen zur MXBean eines Connectors, hier den HTTP-Port 8080, in gekürzter Form, sieht wie folgt aus:
MBean: Catalina:type=Connector,port=8080
modelerType java.lang.String null
maxPostSize int 2097152
scheme java.lang.String http
className java.lang.String null
acceptCount int 100
secure boolean false
...
protocol java.lang.String HTTP/1.1
port int 8080
Und eines für den HTTPS-Port 8443
MBean: Catalina:type=Connector,port=8443
modelerType java.lang.String null
maxPostSize int 2097152
scheme java.lang.String https
secure boolean true
protocol java.lang.String org.apache.coyote.http11.Http11AprProtocol
...
port int 8443
Die MBean-API erlaubt einen rudimentären Querybuilder, mit dem die MBeans entsprechend gefiltert werden können.
Der erste Filter nach "scheme=http" or "scheme=https"
liefert leider auch den AJP-Connector, der über das HTTP-Schema arbeitet. Beim Protokol gibt es die Möglichkeit, zwischen AJP und normalem HTTP zu unterscheiden:
Catalina:port=8080,type=Connector
protocol=HTTP/1.1
Catalina:port=8029,type=Connector
protocol=AJP/1.3
Catalina:port=8443,type=Connector
protocol=HTTP/1.1
Die Erweiterung der Query hilft, lediglich die für HTTP und HTTPS relevanten Ports zu filtern. Leider verwenden unterschiedliche Tomcatversionen verschiedene Werte bei “protocol”. So steht in der aktuellen Tomcat8 Version stets “HTTP/1.1”, in der Tomcat7 steht hier bei HTTPS “org.apache.coyote.http11.Http11AprProtocol”. Entsprechend muss die MBean-Query auch hierzu angepasst werden.
Die vorliegende Klasse ermittelt alle diese Informationen und stellt diese zur Verfügung.
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import javax.management.*;
import javax.servlet.http.HttpServletRequest;
import java.lang.management.ManagementFactory;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.UnknownHostException;
import java.util.*;
/**
* Utility for working with Tomcat.
*
* @author Serhat Cinar
*/
public final class TomcatUtil2 {
private static final Logger LOG = LogManager.getLogger(TomcatUtil2.class);
private static TomcatUtil2 INSTANCE;
private Map<String, Integer> portsByScheme = null;
private TomcatUtil2() {
}
public static synchronized TomcatUtil2 getInstance() {
if (INSTANCE == null) {
INSTANCE = new TomcatUtil2();
try {
INSTANCE.init();
} catch (MalformedObjectNameException | AttributeNotFoundException |
InstanceNotFoundException | MBeanException |
ReflectionException e) {
throw new RuntimeException(e);
}
}
return INSTANCE;
}
/**
* Finds all scheme / port definitiona via MBeans for Catalina.
*
*/
private void init() throws MalformedObjectNameException, AttributeNotFoundException,
InstanceNotFoundException, MBeanException, ReflectionException{
portsByScheme = new HashMap();
final MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer();
findPortsByScheme(portsByScheme, mBeanServer);
logInfo();
}
private void logInfo(){
LOG.info("Initializing scheme -> port mappings: " + portsByScheme);
if (portsByScheme.isEmpty() || portsByScheme.get("http") == null || portsByScheme.get("https") == null) {
final StringBuilder warning = new StringBuilder();
if (portsByScheme.isEmpty()) {
warning.append("No port- or schema-informations found.");
} else {
if (portsByScheme.get("http") == null) {
warning.append("No port- or schema-information found for schema HTTP.");
}
if (portsByScheme.get("https") == null) {
warning.append("No port- or schema-information found for schema HTTPS.");
}
}
LOG.warn(warning.toString());
}
}
/**
* Returns a map with key = scheme (http, https) and value = port.
*
* @return
*/
public Map<String, Integer> getPortsByScheme() {
return Collections.unmodifiableMap(portsByScheme);
}
private static void findPortsByScheme(Map<String, Integer> portsByScheme, MBeanServer mBeanServer)
throws MalformedObjectNameException, AttributeNotFoundException, InstanceNotFoundException, MBeanException,
ReflectionException {
// Filtering by scheme and protocol.
// Protocol String contained sometimes "HTTP/1.1" and sometimes
// the name of the class, like "org.apache.coyote.http11.Http11AprProtocol" (depending on the tomcat Version)
final Set<ObjectName> objectNames = mBeanServer.queryNames(new ObjectName("*:type=Connector,*"),
Query.and(
Query.or(
Query.match(Query.attr("scheme"), Query.value("http")),
Query.match(Query.attr("scheme"), Query.value("https"))
),
Query.or(
Query.anySubString(Query.attr("protocol"), Query.value("HTTP")),
Query.or(
Query.anySubString(Query.attr("protocol"), Query.value("Http")),
Query.anySubString(Query.attr("protocol"), Query.value("http"))
)
)
)
);
for (final ObjectName objectName : objectNames) {
final String scheme = String.valueOf(mBeanServer.getAttribute(objectName, "scheme"));
final String port = objectName.getKeyProperty("port");
portsByScheme.put(scheme, Integer.valueOf(port));
}
}
/**
* Returns the portnumber for the given scheme (http or https).
*
* @param scheme "http" or "https"
* @return
*/
public Integer getPortByScheme(final String scheme) {
return getPortsByScheme().get(scheme);
}
/**
* Returns a HTTP-URL for the given httpRequest, whether it's HTTP or HTTPS.<br/>
* {@code TomcatUtil.getInstance().generateHttpUrl( myHttpRequest, "page.html") }
* will generate "http://localhost:8080/myApp/page.html" on most default configurations,
* when the httpRequest belongs to "myApp" and the page is set to "page.html".
*
* @param httpRequest
* @param page
* @return
* @throws MalformedURLException
*/
public String generateHttpUrl(final HttpServletRequest httpRequest, final String page) throws MalformedURLException {
return generateUrl(httpRequest, page, "http");
}
/**
* Returns a HTTPS-URL for the given httpRequest, whether it's HTTP or HTTPS.<br/>
* {@code TomcatUtil.getInstance().generateHttpsUrl( myHttpRequest, "page.html") }
* will generate "https://localhost:8443/myApp/page.html" on most default configurations,
* when the httpRequest belongs to "myApp" and the page is set to "page.html".
*
* @param httpRequest
* @param page
* @return
* @throws MalformedURLException
*/
public String generateHttpsUrl(final HttpServletRequest httpRequest, final String page) throws MalformedURLException {
return generateUrl(httpRequest, page, "https");
}
/**
* Returns a HTTP/HTTPS-URL for the given httpRequest, whether it's HTTP or HTTPS.
*
* @param httpRequest
* @param page
* @return
* @throws MalformedURLException
* @see #generateHttpsUrl(HttpServletRequest, String)
* @see #generateHttpUrl(HttpServletRequest, String)
*/
public String generateUrl(final HttpServletRequest httpRequest, final String page, final String scheme) throws MalformedURLException {
String targetPage = page;
if (targetPage != null && !targetPage.startsWith("/")) {
targetPage = "/" + targetPage;
}
final String requestUrlStr = httpRequest.getRequestURL().toString();
final URL requestUrl = new URL(requestUrlStr);
final URL secureUrl = new URL(scheme, requestUrl.getHost(), getPortByScheme(scheme), httpRequest.getContextPath() + targetPage);
return secureUrl.toString();
}
/**
* See {@link #generateHttpUrl(HttpServletRequest, String)}.<br/>
* Here page will be set to {@code httpRequest.getServletPath()}.
*
* @param httpRequest
* @return
* @throws MalformedURLException
* @see #generateHttpUrl(HttpServletRequest, String)
*/
public String generateHttpUrl(final HttpServletRequest httpRequest) throws MalformedURLException {
return generateHttpUrl(httpRequest, httpRequest.getServletPath());
}
/**
* See {@link #generateHttpsUrl(HttpServletRequest, String)}.<br/>
* Here page will be set to {@code httpRequest.getServletPath()}.
*
* @param httpRequest
* @return
* @throws MalformedURLException
* @see #generateHttpUrls(HttpServletRequest, String)
*/
public String generateHttpsUrl(final HttpServletRequest httpRequest) throws MalformedURLException {
return generateHttpsUrl(httpRequest, httpRequest.getServletPath());
}
}