简体   繁体   English

如何在Tomcat环境中保存名称 - 值对?

[英]How do you save name-value pairs in Tomcat environment?

We have a servlet that needs certain variables like passwords, encryption salts, etc., not to be saved on the file system permanently. 我们有一个servlet需要某些变量,如密码,加密盐等,不能永久保存在文件系统中。 This is what we do currently (summary): 这就是我们目前的工作(摘要):

During initialization, 在初始化期间

  1. A Perl script sets ReadMode to 2 to mask stdout echo, prompts user for the variables, filters a known file to put them in and invokes tomcat/bin/startup.sh Perl脚本将ReadMode设置为2以屏蔽stdout echo,提示用户输入变量,过滤已知文件以放入它们并调用tomcat / bin / startup.sh

  2. The servlet init() method reads the variables from the file and deletes it (the file). servlet init()方法从文件中读取变量并删除它(文件)。

Problem: When the WAR is recompiled, tomcat tries to deploy it (autodeploy=true), which we want. 问题:当重新编译WAR时,tomcat会尝试部署它(autodeploy = true),这是我们想要的。 But the data file is no longer there, so a FileNotFoundException is thrown (rightly so). 但是数据文件不再存在,因此抛出了FileNotFoundException(正确地说是这样)。

Question: Is there a property or some HashMap/Table available to servlets where a few variables can be stored during the manual startup? 问题:servlet可以使用属性或某些HashMap / Table,在手动启动期间可以存储一些变量吗? The idea is that init() could check for them if the data file is not there during redeployment. 我们的想法是,如果在重新部署期间数据文件不存在,init()可以检查它们。 Thank you, - MS. 谢谢, - MS。

How about passing them in as system properties when you call startup.sh, ie. 当你调用startup.sh时,如何将它们作为系统属性传递,即。 'bin/startup.sh -DencryptionSalt=foobar'? 'bin / startup.sh -DencryptionSalt = foobar'? Then you can call System.getProperty("encryptionSalt") for the duration of the JVM. 然后,您可以在JVM的持续时间内调用System.getProperty(“encryptionSalt”)。

Put your volatile data into JNDI. 将易失性数据放入JNDI。 JNDI isn't cleaned up between redeployments. 重新部署之间不会清除JNDI。 Your servlet could still do the same thing during init to ensure the data in JNDI is up-to-date. 您的servlet仍然可以在init期间执行相同的操作,以确保JNDI中的数据是最新的。

I can't remember if JNDI reads/writes are thread safe, but it it isn't, you can always put a thread-safe object (eg ConcurrentHashMap ) and use it without problems. 我不记得JNDI读/写是否是线程安全的,但事实并非如此,你总是可以放一个线程安全的对象(例如ConcurrentHashMap )并毫无问题地使用它。

EDIT by MS: 编辑由MS:

==========
restart.pl:
==========
ReadMode 2; print "\nEnter spice: "; chomp ($spice = <STDIN>); print "\n";
ReadMode 0;
open (FP, ">$spicefile") or die "$0: Cannot write file $spicefile: $!\n";
print FP "$spice\n"; close (FP);
system "bin/shutdown.sh";       sleep 8;
system "bin/startup.sh";        sleep 8;        system "wget $tomcaturl";
system '/bin/rm -rf *spicefile*';               # delete wget output file
foreach $sCount (1..10) {                       # give it 10 more secs
    sleep 1;
    if (-f $spicefile) {
        print "$0: Waiting on servlet to delete spicefile [$sCount]\n"
    } else {
        print "$0: Successful servlet initialization, no spicefile\n";
        exit 0
}}
print "\n$0: deleting file $spicefile ...\n";   # Error condition
system "unlink $spicefile";
exit 1;

===========
context.xml
===========
    <Resource name="MyServlet/upsBean" auth="Container" type="packageName.UPSBean"
                factory="org.apache.naming.factory.BeanFactory" readOnly="false"/>

===================
app/WEB-INF/web.xml
===================
  <listener>
        <listener-class>packageName.DBInterface</listener-class>
  </listener>
  <resource-env-ref>
    <description>Use spice as transferable during redeployment</description>
    <resource-env-ref-name>MyServlet/upsBean</resource-env-ref-name>
    <resource-env-ref-type>packageName.UPSBean</resource-env-ref-type>
  </resource-env-ref>

==============
MyServlet.java:
==============
init() {
        if (new File (spiceFile).exists())      # Same name in restart.pl
                dbi = new DBInterface (spiceFile);
        else
                dbi = new DBInterface ();       # Redeployment
        otherInitializations (dbi.getSpice());
}

================
DBInterface.java:
================
public DBInterface () {
        // Comment out following block if contextInitialized works
        FileInputStream fin = new FileInputStream (safeDepositBox);
        ObjectInputStream ois = new ObjectInputStream (fin);
        UPSBean upsBean = (UPSBean) ois.readObject();
        ois.close();
        spice = upsBean.getSpice();
        dbiIndex = 2;
        // do stuff with spice
}

public DBInterface (String spiceFileName) {
        File file = new File (spiceFileName);
        BufferedReader br = new BufferedReader (new FileReader (file));
        spice = br.readLine();
        br.close();
        file.delete();
        dbiIndex = 1;

        // Delete following block if contextInitialized works
        UPSBean upsBean = new UPSBean();
        upsBean.setSpice (spice);
        FileOutputStream fout = new FileOutputStream (safeDepositBox);
        ObjectOutputStream oos = new ObjectOutputStream (fout);
        oos.writeObject (upsBean);
        oos.flush();
        oos.close();
        // do stuff with spice and if it works, ...
        // contextInitialized (null);
}

// Above is working currently, would like the following to work

public void contextDestroyed(ServletContextEvent sce) {
        System.setProperty ("spice", spice);
        System.out.println ("[DBInterface" + dbiIndex +
                                        "] Spice saved at " +
                        DateFormat.getDateTimeInstance (DateFormat.SHORT,
                                        DateFormat.LONG).format (new Date()));
}

public void contextInitialized(ServletContextEvent sce) {
        if (sce != null) {
                spice = System.getProperty ("spice");
                System.out.println ("[DBInterface" + dbiIndex +
                                        "] Spice retrieved at " +
                        DateFormat.getDateTimeInstance (DateFormat.SHORT,
                                        DateFormat.LONG).format (new Date()));
        }
        // do stuff with spice
}

============
UPSBean.java:
============
public class UPSBean implements Serializable {
        private String  spice = "parsley, sage, rosemary and thyme";
        public UPSBean() { }
        public String getSpice() {      return spice;   }
        public void setSpice (String s) {       spice = s;      }
}

I am trying to see if get/setProperty works above. 我试图看看get / setProperty是否在上面工作。 Was trying to use JNDI directly, but when I setSpice using the resource MyServlet/upsBean in contextDestroyed() and try to read it in contextInitialized(), I get a null (Sorry, I already deleted that part of the code). 试图直接使用JNDI,但是当我使用contextDestroyed()中的资源MyServlet / upsBean setSpice并尝试在contextInitialized()中读取它时,我得到一个null(抱歉,我已经删除了那部分代码)。 Now the declaration in context.xml as well as resource-env-ref has become redundant. 现在context.xml中的声明以及resource-env-ref已经变得多余。 Current workaround is to save the serialized instance in a data file (not very good). 当前的解决方法是将序列化实例保存在数据文件中(不是很好)。

If you want to handle it programmatically, I think what you might be looking for is a ServletContextListener . 如果您想以编程方式处理它,我认为您可能正在寻找的是ServletContextListener Create a class that implements the interface and code the needed functionality in the contextInitialized(ServletContextEvent sce) -method, see here for simple example. 创建一个实现接口的类,并在contextInitialized(ServletContextEvent sce) -method中编写所需的功能,请参见此处的简单示例。

Tomcat provides nothing to help you here. Tomcat在这里没有提供任何帮助。

Thats the bad news. 这是个坏消息。

The good news is ... this is a process issue which can be solved using the tools the servlet-api provides as well as some additional items Tomcat can provide. 好消息是......这是一个流程问题,可以使用servlet-api提供的工具以及Tomcat可以提供的一些其他项来解决。 Here are the ingredients .. 这是成分..

  • Tomcat provides on container startup the the ability to have Listener - via the Listener element. Tomcat在容器启动时为Listener提供了通过Listener元素的能力。 Use these to track when the container starts up, shuts down, etc. 使用这些来跟踪容器何时启动,关闭等。
  • The servlet api provides ServletContextListener to listen for webapp startup, shutdown servlet api提供ServletContextListener来监听webapp启动,关闭
  • When tomcat starts up - you may pass in System properties by first setting JAVA_OPTS. 当tomcat启动时 - 您可以通过首先设置JAVA_OPTS来传递系统属性。 But be careful - these values can be discovered with the ps command. 但要小心 - 可以使用ps命令发现这些值。
  • Depending on your deployment method - you can add settings for your configuration via my-app.xml (where my-app is the name of the app for deployment) 根据您的部署方法 - 您可以通过my-app.xml添加配置设置(其中my-app是部署应用程序的名称)
  • In CATALINA_BASE/conf/context.xml - you may also set init/env params for the webapp to use for ALL webapps to see. 在CATALINA_BASE / conf / context.xml中 - 您还可以为webapp设置init / env参数,以供所有Web应用程序查看。
  • If you are NOT using a security manager - you should be able to set/clear some system properties. 如果您不使用安全管理器 - 您应该能够设置/清除某些系统属性。

Given all of the above - you could read the "protected" values from a file and write them to a system property. 鉴于以上所有 - 您可以从文件中读取“受保护”值并将其写入系统属性。 Then for added protection in case you don't want the values always existing as System properties - you can utilize ServletContextListener to read the system property values and delete them from system properties. 然后,为了增加保护,以防您不希望值始终作为系统属性存在 - 您可以使用ServletContextListener读取系统属性值并从系统属性中删除它们。 Then on re-deployment - the listener would write reset the system properties on shutdown - so when the webapp restarts up - they are still there. 然后在重新部署时 - 侦听器会在关闭时写入重置系统属性 - 所以当webapp重新启动时 - 它们仍然存在。

So the startup sequence would be like this 所以启动顺序就是这样

  • Admin enters pw (existing process) and the file is saved 管理员输入pw(现有流程)并保存文件
  • On webapp startup - ServletContextListner looks for file. 在webapp启动时 - ServletContextListner查找文件。 If exists - uses those values 如果存在 - 使用这些值
  • On webapp shutdown - writes the values to System.properties 在webapp shutdown上 - 将值写入System.properties
  • On webapp restart - ServletContextListner sees the file is missing and uses the System.properties. 在webapp restart上 - ServletContextListner看到文件丢失并使用System.properties。 Then deletes the system.properties 然后删除system.properties

With the above - you can redeploy while not writing the passwords to disk and hopefully minimizing any attack vectors by temporarily storing as system.properties. 有了上述内容 - 您可以重新部署,而不是将密码写入磁盘,并希望通过临时存储为system.properties来最小化任何攻击媒介。

Good luck. 祝好运。

There is nothing in Tomcat to do what you want. Tomcat中没有任何东西可以做你想做的事。 If you move the settings into the tomcat JNDI tree you will have to put the user/name password combo in the server.xml or the context.xml file. 如果将设置移动到tomcat JNDI树中,则必须将用户/名称密码组合放在server.xml或context.xml文件中。 Here are a couple of possible solutions for the problem you have. 以下是针对您遇到的问题的几种可能的解决方案。

Option 1: Use a Tomcat Listener 选项1:使用Tomcat侦听器

If you look at the top of the tomcat server.xml file you will see several listeners these are java classes that execute when tomcat starts up. 如果查看tomcat server.xml文件的顶部,您将看到几个侦听器,这些是在tomcat启动时执行的java类。 You can create you own tomcat listener which reads the password from the file system, deletes the file and stores the username/password Comobo in a way that is accessible to the app. 您可以创建自己的tomcat侦听器,该侦听器从文件系统读取密码,删除文件并以应用程序可访问的方式存储用户名/密码Comobo。 The tomcat listener is tied to the lifecyle of the tomcat server so auto redeploy of the app will not cause your tomcat listener class to be reloaded. tomcat监听器绑定到tomcat服务器的生命周期,因此应用程序的自动重新部署不会导致重新加载tomcat监听器类。 The code for your tomcat listener would have to be put into a jar and place in the CATALINA_HOME\\lib folder. 您的tomcat侦听器的代码必须放入jar并放在CATALINA_HOME \\ lib文件夹中。

The listener could use static variables to store the username/password and have a static methods that return them, this will work because the listener will be in the parent class loader of the tomcat apps. 侦听器可以使用静态变量来存储用户名/密码并使用返回它们的静态方法,这将起作用,因为侦听器将位于tomcat应用程序的父类加载器中。 The disadvantages of this approach is that you application code is dependent on the tomcat resource listener implementation, and in your unit tests you might have to do some extra steps to make sure your code can be unit tested. 这种方法的缺点是您的应用程序代码依赖于tomcat资源侦听器实现,并且在您的单元测试中,您可能需要执行一些额外的步骤以确保您的代码可以进行单元测试。

The listener could also access the tomcat global JNDI tree and put the username and password combo in there, and then your app would have to have context.xml file use a ResourceLink element to make the global jndi entry available to the app. 监听器还可以访问tomcat全局JNDI树并将用户名和密码组合放在那里,然后你的应用必须让context.xml文件使用ResourceLink元素来使应用程序可以使用全局jndi条目。 You will also have to do some extra work to make this approach work with unit tests as looking stuff up in JNDI generally complicates unit testing. 您还需要做一些额外的工作来使这种方法与单元测试一起工作,因为在JNDI中查找内容通常会使单元测试变得复杂。

If you are using Spring on this project your best bet is to use the static variables in a tomcat listener but then use a custom spring scope to pull the data out of the tomcat listener. 如果您在此项目中使用Spring,最好的办法是在tomcat侦听器中使用静态变量,然后使用自定义弹簧范围将数据从tomcat侦听器中提取出来。 that way your app stays testable and you can inject username/password combos into any piece of code that needs them. 这样你的应用程序可以测试,你可以将用户名/密码组合注入任何需要它们的代码片段。

Option 2: Use a Servlet Context Listener 选项2:使用Servlet上下文侦听器

In this option you write a Context Listener which will allow your app to be notified whenever the app starts and stops. 在此选项中,您可以编写一个上下文侦听器,它将允许您的应用在应用启动和停止时得到通知。 With this approach on startup the context listener will run and read the password information and delete the file. 使用此方法启动时,上下文侦听器将运行并读取密码信息并删除该文件。 If the password file is not there on startup then the context listener would have to have a way to get the admin to regenerate the file. 如果启动时没有密码文件,那么上下文监听器必须有办法让管理员重新生成文件。

Option 3: Use JMX 选项3:使用JMX

Create a JMX MBean register it with the JVM MBeanServer and then use it to store the username/password combo. 创建一个JMX MBean,将其注册到JVM MBeanServer,然后使用它来存储用户名/密码组合。 If you initialize this MBean from a tomcat listener you could then have the perl script call the MBean remotely and pass in the username/password combo. 如果从tomcat侦听器初始化此MBean,则可以让perl脚本远程调用MBean并传入用户名/密码组合。

Hope this helps. 希望这可以帮助。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM