Tomcat:以编程方式更改虚拟主机?

Posted

技术标签:

【中文标题】Tomcat:以编程方式更改虚拟主机?【英文标题】:Tomcat: Change the Virtual hosts programmatically? 【发布时间】:2010-12-16 06:48:09 【问题描述】:

Tomcat 提供了“虚拟主机”支持:引擎/Web 应用程序可以配置为负责域列表。必须使用特殊的 xml 指令将这些域放入 server.xml/context.xml 文件中。

=> 是否有可能以编程方式更改 Tomcat 配置(通常),尤其是 Web 应用程序/引擎的“虚拟主机”?

例如,如果一个新用户注册,我必须将他的域添加到“接受的虚拟主机/域”列表中。我目前想到的唯一方法是通过脚本更改xml文件,然后重新启动Tomcat。

有什么方法可以通过一些 Java 方法以编程方式添加它们添加运行时?

非常感谢! 一月

【问题讨论】:

【参考方案1】:

Tomcat 提供 API 来创建新的虚拟主机。要访问为此所需的包装器对象,您需要实现一个 ContainerServlet。你可以像这样创建虚拟主机,

    Context context = (Context) wrapper.getParent();
    Host currentHost = (Host) context.getParent();
    Engine engine = (Engine) currentHost.getParent();

    StandardHost host = new StandardHost();
    host.setAppBase(appBase);
    host.setName(domainName);

    engine.addChild(host);

您需要确保 appBase 目录存在,并且您必须找到将新主机持久保存到 server.xml 的方法,否则您会在重新启动时丢失主机。

但是,这种方法很少奏效。如果您的用户运行他们自己的应用程序,那么您确实希望运行单独的 Tomcat 实例,以便更好地对应用程序进行沙箱处理。例如一个应用程序内存不足并不会杀死所有其他应用程序。

如果您提供应用程序,您可以只使用一台主机(默认主机)。您可以从 Host 标头获取域名,并在代码中执行任何特定于域的内容。

【讨论】:

您好 ZZ Coder,感谢您的回答。这很有趣,从来没有听说过,知道有办法访问 Tomcat 中的主机和引擎真是太棒了。 @Coder,如何获取wrapper对象 @Kainix 你需要创建一个实现ContainerServlet的servlet,它有一个setter setWrapper()。 Tomcat 将调用您的设置器来设置包装器。我从 Tomcat 5 开始就没有使用过这个,所以机制可能已经改变了。 @Coder,我实现了ContainerServlet 创建的appBase 目录,因为我在本地测试这个我在hostshosts 文件中添加了domainName /drivers/etc/hosts 但是当我运行时这个然后把domainName 放在浏览器中它会给出空白页。我错过了什么吗? @Kainix 我做到了并在这里发布了结果。【参考方案2】:

您不应该以编程方式更改服务器环境,并且没有可靠且标准的方法可以做到这一点。最好的做法是把它全部放在 webapp 端。首先,Filter 非常适合此操作。将名称存储在您缓存在应用程序范围内的数据库表或属性文件中的某处。检查HttpServletRequest#getRequestURI()(或getServerName(),如果它是子域而不是路径信息)并相应地执行转发任务。

希望这会有所帮助。

【讨论】:

我认为这是最好的方法。为什么需要数据库映射。我们不能简单地将网络应用映射到子域吗?【参考方案3】:

使用 JMX

ArrayList serverList = MBeanServerFactory.findMBeanServer(null);
MBeanServer server = (MBeanServer) serverList.get(0);
Object[] params =  "org.apache.catalina.core.StandardHost", hostName ;
String[] signature =  "java.lang.String", "java.lang.String" ;
server.invoke(new ObjectName("Catalina:type=Engine"), "addChild", params, signature);

如果需要,检索主机对象并使用它:

ObjectName host = new ObjectName("Catalina:type=Host,host=" + hostName);
server.setAttribute(host, new Attribute("autoDeploy", false));
server.invoke(host, "start", null, null);

【讨论】:

感谢您的帖子!请不要在您的帖子中使用签名/标语。您的用户框算作您的签名,您可以使用您的个人资料发布您喜欢的任何关于您自己的信息。 FAQ on signatures/taglines【参考方案4】:

我建议您将应用程序设置为 server.xml 中的默认虚拟主机,这样您的单个虚拟主机就可以响应发往任何主机名的请求。 Tomcat 将 localhost 应用程序设置为默认虚拟主机。因此,您可以通过简单地检查 vanilla tomcat 安装的 server.xml 文件来了解如何执行此操作。您可以使用ServletRequest.getServerName() 方法以编程方式确定用户将请求发送到的主机名。

Tomcat 曾经附带一个名为“host-manager”的 Web 应用程序。注意:这与 Tomcat 仍然附带的“管理器”Web 应用程序不同。主机管理器允许在不重新启动服务器的情况下即时更改配置或添加新的虚拟主机。您可以通过 HTTP 与主机管理器交互(如果需要,以编程方式)。但是,它有一个不幸的缺陷是没有将其更改提交到 server.xml,因此它们在 Web 服务器重新启动时都丢失了。无论出于何种原因,从版本 6 开始,Tomcat 不再附带主机管理器应用程序。所以它似乎不再受支持了。

【讨论】:

【参考方案5】:

总结ZZ Coder的答案对我有很大的指导:

您必须创建一个实现ContainerServlet 的servlet 并覆盖setWrapper 方法以获取org.apache.catalina.Wrapper 对象。

为此,您必须在context.xml Context 标记中包含privileged="true",否则会引发异常。然后你可以使用Wrapper 对象和:

StandardContext context = (StandardContext) wrapper.getParent();
StandardHost currentHost = (StandardHost) context.getParent();
StandardEngine engine = (StandardEngine) currentHost.getParent();

StandardHost host = new StandardHost();
host.setAppBase(currentHost.getAppBase()); //in my case I created another instance of the same application
host.setDomain(currentHost.getDomain());
host.setAutoDeploy(false); // not restarting app whenever changes happen
host.setName("domain.com");
host.setThrowOnFailure(true);// tell it to throw an exception here if it fails to create the host
host.setDeployOnStartup(true);
host.setStartChildren(true);
host.setParent(engine); 
// you can add multiple aliases
host.addAlias(alias);

StandardContext ctx = new StandardContext();
ctx.setDocBase(context.getDocBase()); //again I reused my same application setting
ctx.setPath("");
if(currentHost.getWorkDir() != null)
//create a working directory based on your new host's name
    ctx.setWorkDir(currentHost.getWorkDir().replace(currentHost.getName(), host.getName()));

ctx.setName(host.getDomain());

//some extra config that you can use
ctx.setUseHttpOnly(false);
ctx.setReloadable(false);
ctx.setXmlValidation(false);
ctx.setXmlNamespaceAware(false);
ctx.setCrossContext(false);
ctx.setParent(host);
// you have to have this or it will not work!!
ctx.addLifecycleListener(new ContextConfig()); 

//you can also create resources and add it to the context like so:
final ContextResource res = new ContextResource();
res.setName("name");
res.setAuth("Container");
res.setType("javax.sql.DataSource");
ctx.getNamingResources().addResource(res);

host.addChild(ctx);

engine.addChild(host);

您可以通过调用res.setProperty("name", "value") 向您的资源添加属性 您可以使用的一些属性是: initialSize,maxTotal,maxIdle,maxWaitMillis,removeAbandonedOnBorrow,removeAbandonedTimeout,validationQuery,timeBetweenEvictionRunsMillis,driverClassName,url,url,@98765434 p>

另一个令人兴奋的事情是通过调用engine.findChild(domain) 并使用stop()start()getStateName() 从 Tomcat 引擎获取主机,并拥有自己的 Tomcat 管理面板!

【讨论】:

以上是关于Tomcat:以编程方式更改虚拟主机?的主要内容,如果未能解决你的问题,请参考以下文章

Windows 上的虚拟/以编程方式生成的文件?

jetty:如何以编程方式配置多个虚拟主机?

以编程方式在 linux 上触发 inotify 事件

以编程方式在 Wildfly/Undertow 中创建虚拟主机

springboot-为内置tomcat设置虚拟目录

如何以编程方式在共享主机计划上创建插件域