从Tomcat中获取服务器端口号而不需要请求

37

是否有任何Tomcat API或配置可以在应用程序启动时告诉它正在运行的端口,而不需要请求?

假设有两个Web应用程序在同一个Tomcat服务器上运行,其中一个需要调用另一个Web服务。我们不希望请求离开Tomcat服务器(如果使用Apache服务器名称或绝对URL,则请求将出去并返回,可以转到任何实例),然后再返回。我知道机器的名称,但无法获取端口号。我知道可以硬编码这些信息,但我不想这样做,因为我希望我的war文件适用于所有应用服务器。

我知道如果有HTTPServletRequest,我们可以找到它。

这仅适用于Tomcat 6,并且在Tomcat 7上不起作用。


我不确定为什么应用程序需要知道端口。您能否提供一些关于为什么需要这样做的额外细节? - David
1
您需要所有可用端口还是特定端口?应用服务器可以监听多个端口(例如,请考虑 HTTP 端口 80 和 HTTPS 端口 443)。 - Codemwnci
@Sean,我不是完全确定。即使我们可以从MBean获取详细信息,也应该有一个MBean的url:port,对吧?请详细说明并提供可能的示例。 - Teja Kantamneni
@Teja 我已经更新了我的答案来解决你的问题。 - Puspendu Banerjee
1
另一个有效的用例是在向远程系统发出请求并需要提供回调地址到Web应用程序时。在我的情况下,没有先前的传入请求允许我动态识别传入端口。以下解决方案建议都不够通用或服务器无关,所以我认为我将不得不不幸地从属性文件中注入端口。 - John Rix
你知道如何在Tomcat 10上做这件事吗? - user1133275
12个回答

30

通过这个功能:

List<String> getEndPoints() throws MalformedObjectNameException,
        NullPointerException, UnknownHostException, AttributeNotFoundException,
        InstanceNotFoundException, MBeanException, ReflectionException {
    MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
    QueryExp subQuery1 = Query.match(Query.attr("protocol"), Query.value("HTTP/1.1"));
    QueryExp subQuery2 = Query.anySubString(Query.attr("protocol"), Query.value("Http11"));
    QueryExp query = Query.or(subQuery1, subQuery2);
    Set<ObjectName> objs = mbs.queryNames(new ObjectName("*:type=Connector,*"), query);
    String hostname = InetAddress.getLocalHost().getHostName();
    InetAddress[] addresses = InetAddress.getAllByName(hostname);
    ArrayList<String> endPoints = new ArrayList<String>();
    for (Iterator<ObjectName> i = objs.iterator(); i.hasNext();) {
        ObjectName obj = i.next();
        String scheme = mbs.getAttribute(obj, "scheme").toString();
        String port = obj.getKeyProperty("port");
        for (InetAddress addr : addresses) {
            if (addr.isAnyLocalAddress() || addr.isLoopbackAddress() || 
                addr.isMulticastAddress()) {
                continue;
            }
            String host = addr.getHostAddress();
            String ep = scheme + "://" + host + ":" + port;
            endPoints.add(ep);
        }
    }
    return endPoints;
}

您将会获得一个类似于下面这样的列表:
[http://192.168.1.22:8080]

24

对于任何对我们如何解决这个问题感兴趣的人,这里是模拟代码

Server server = ServerFactory.getServer();
        Service[] services = server.findServices();
        for (Service service : services) {
            for (Connector connector : service.findConnectors()) {
                ProtocolHandler protocolHandler = connector.getProtocolHandler();
                if (protocolHandler instanceof Http11Protocol
                    || protocolHandler instanceof Http11AprProtocol
                    || protocolHandler instanceof Http11NioProtocol) {
                    serverPort = connector.getPort();
                    System.out.println("HTTP Port: " + connector.getPort());
                }
            }


        }

8
不适用于Tomcat-7 - 请参考https://dev59.com/n2w15IYBdhLWcg3wFH3g - Mr_and_Mrs_D
不在 JBoss AS 7 上 =\ - thiagoh
我不得不使用connector.getLocalPort()而不是connector.getPort(),因为当我将其配置为使用随机端口时,后者返回0。 - Jonas Berlin
3
ServerFactory在哪个包中?(在Tomcat 10中没有看到它) - user1133275

8
public void getIpAddressAndPort() 
throws MalformedObjectNameException, NullPointerException,
            UnknownHostException {

        MBeanServer beanServer = ManagementFactory.getPlatformMBeanServer();

        Set<ObjectName> objectNames = beanServer.queryNames(new ObjectName("*:type=Connector,*"),
                Query.match(Query.attr("protocol"), Query.value("HTTP/1.1")));

        String host = InetAddress.getLocalHost().getHostAddress();
        String port = objectNames.iterator().next().getKeyProperty("port");

        System.out.println("IP Address of System : "+host );
        System.out.println("port of tomcat server : "+port);

    }

1
虽然这段代码可能回答了问题,但提供关于它如何以及为什么解决问题的额外上下文会提高答案的长期价值。- 来自审查 - Michael Parker

2
服务器端口号不存在。它可以有任意数量的端口号。因此,您所询问的没有意义。与特定请求相关联的端口号确实有意义。

@downvoter 请解释一下。我有正在运行的生产Tomcat,每个Tomcat使用五个端口。在这种情况下,“服务器端口号”具体是什么意思? - user207421

1

这些类型的服务器旨在能够监听(几乎)任意端口,并隐藏这些细节,让包含的应用程序通常不需要知道。

唯一的方法是自己读取配置文件并访问启动服务器的命令行参数,在此处可能已覆盖配置文件。您必须对正在运行的系统有很多了解才能使其正常工作。没有便携式的方法。

即使有,也有一些情况根本不重要,比如在NAT、某些防火墙等后面。


1
  • 获取Tomcat/服务器实例的MBean/JMX对象
  • 从那里获取虚拟服务器实例相关数据

请参考http://svn-mirror.glassfish.org/glassfish-svn/tags/embedded-gfv3-prelude-b07/web/web-glue/src/main/java/com/sun/enterprise/web/WebContainer.java

MBeanServer的内容可以通过各种协议连接器[RMI/IIOP]或协议适配器[SNMP/HTTP]来公开。在这种情况下,使用SNMP适配器将是更好的方法,以便可以放置SNMP陷阱而不知道其他应用程序服务器的确切IP/端口。


1

您可以使用crossContext。但我认为这不是应用服务器无关的。

我会分享一个自定义类,通过JNDI表现为同一Tomcat实例中正在运行的应用程序的注册表,就像我在here中解释的那样。

在启动期间,通过ContextListener或通过Spring容器事件,我将通过JNDI查找获取注册表,使用从servletcontext.contextpath获得的url添加我的Web应用程序实例,最后注册一个侦听器以便听取其他应用程序注册自己。这是我能想到的更多的服务器无关性。

获取端口将不是服务器无关的,您应该使用上下文参数。

编辑:很抱歉,忘记说我所描述的是在上下文之间共享对象,但是,除非您使用某些服务器API(根本不是无关的),否则您不能知道端口。


1
public String getPort() {
    MBeanServer beanServer = ManagementFactory.getPlatformMBeanServer();
    Set<ObjectName> objectNames;
    try {
        objectNames = beanServer.queryNames(new ObjectName("*:type=ProtocolHandler,*"),
                Query.match(Query.attr("name"), Query.value("\"http-*")));
    } catch (MalformedObjectNameException e) {
        LOGGER.error("Port not defined!", e);
    }

    return objectNames.iterator().next().getKeyProperty("port");
}

public String getSecurePort() {
    MBeanServer beanServer = ManagementFactory.getPlatformMBeanServer();
    Set<ObjectName> objectNames;
    try {
        objectNames = beanServer.queryNames(new ObjectName("*:type=ProtocolHandler,*"),
                Query.match(Query.attr("name"), Query.value("\"https-*")));
    } catch (MalformedObjectNameException e) {
        LOGGER.error("SecuredPort not defined!", e);
    }

    return objectNames.iterator().next().getKeyProperty("port");
}

0
如果您想访问同一服务器实例上的应用程序,只需省略URL的服务器部分。以下是一些示例,您可以实现以下操作。当前文档位于http://example.com:8080/app2/doc.html
  • xxx.html 变为 http://example.com:8080/app2/xxx.html
  • ../xxx.html 变为 http://example.com:8080/xxx.html
  • ../xxx.html 变为 http://example.com:8080/xxx.html
  • ../foo/xxx.html 变为 http://example.com:8080/foo/xxx.html
  • ../../xxx.html 变为 http://example.com:8080/xxx.html(无法超越根目录)
  • /xxx.html 变为 http://example.com:8080/xxx.html 这可能是您要寻找的。
  • //other.com/xxx.html 变为 http://example.com:8080/xxx.html 如果您想保持“https:”很有用。

但问题是关于检测监听端口。 - Puspendu Banerjee
1
不,问题实际上是如何访问同一服务器上的另一个应用程序。 - Aaron Digulla
Teja说:“我知道如果我们有一个HTTPServletRequest,我们就可以找到它”,而你的回答暗示着一个传入的请求。 - Martín Schonaker
1
@mrrtnn:叹气他不需要一个端口号。他只需要访问所需服务的相对URL 在他需要访问它的时候 - Aaron Digulla

0
在之前的一个大型分布式项目中,我使用的设计是让集中式服务使用中央服务的URL和端口初始化多个服务。
显然,这意味着中央服务必须维护一个服务列表(URL和端口)以进行初始化。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接