Tomcat Manager

2018年07月27日 08:49 | 2537次浏览

概述

很多生产环境都非常需要以下特性:在无需关闭或重启整个容器的情况下,部署新的 Web 应用或者取消对现有应用的部署。或者,即便在 Tomcat 服务器配置文件中没有指定 reloadable 的情况下,也可以请求重新加载现有应用。

Tomcat 中的 Web 应用 Manager 就是来解决这些问题的,它默认安装在上下文路径:/manager 中,支持以下功能:

  • 用已上传的 WAR 文件内容部署新的 Web 应用。

  • 在服务器文件系统中指定上下文路径处部署新的 Web 应用。

  • 列出当前已部署的 Web 应用,以及这些应用目前的活跃会话。

  • 重新加载现有的 Web 应用,以便响应 /WEB-INF/classes 或 /WEB-INF/lib 中内容的更改。

  • 列出操作系统及 JVM 的属性值。

  • 列出可用的全局 JNDI 资源,它们将用于预备 <ResourceLink> 元素的部署工具中。<ResourceLink> 元素内嵌于 <Context> 部署描述中。

  • 开启一个已停止的 Web 应用,从而使其再次可用。

  • 停止一个现有的 Web 应用,从而使其不可用,但并不取消对它的部署。

  • 取消对一个已部署 Web 应用的部署,删除它的文档库目录(除非它是从文件系统中部署的)。

Tomcat 默认安装已经包含了 Manager。 将一个 Manager 应用实例的 Context 添加到一个新的主机中,manager.xml 上下文配置文件应放在 $CATALINA_BASE/conf/[enginename]/[hostname] 文件夹中。如下所示:

<Context privileged="true" antiResourceLocking="false"
         docBase="${catalina.home}/webapps/manager">
  <Valve className="org.apache.catalina.valves.RemoteAddrValve"
         allow="127\.0\.0\.1" />
</Context>

如果将 Tomcat 配置成能够支持多个虚拟主机(网站),则需要对每个虚拟主机配置一个 Manager。

Manager 应用的使用方式有以下三种:

  • 作为带有用户界面的应用,在浏览器中运行。在随后这个范例 URL 中,你可以将 localhost 替换为你的网站主机名称:http://localhost:8080/manager/html

  • 只使用 HTTP 请求的一个功能最少的版本。它适合系统管理员通过创建脚本来进行使用。将命令指定在请求的 URI 中,响应是简单格式的文本(易于解析与处理)。详情查看  利用 Ant 执行 Manager 命令

配置 Manager 应用访问

下文的描述将使用变量名 $CATALINA_BASE 来指代工作目录。如果你还没有为多个 Tomcat 实例设置 CATALINA_BASE 目录,那么 $CATALINA_BASE 就将被设置为 $CATALINA_HOME(Tomcat 的安装目录)的值。

Tomcat 以默认值运行是非常危险的,因为这能让互联网上的任何人都可以在你的服务器上执行 Manager 应用。因此,Manager 应用要求任何用户在使用前必须验证自己的身份,提供自己的用户名和密码,以及相应配置的 manager-** 角色(角色名称根据所需的功能而定)。另外,默认用户文件($CATALINA_BASE/conf/tomcat-users.xml)中的用户名称都没有指定角色名称,所以默认是不能访问 Manager 应用的。

这些角色名称位于 Manager 应用的 web.xml 文件中。可用的角色包括:

  • manager-gui 能够访问 HTML 界面。

  • manager-status 只能访问“服务器状态”(Server Status)页面。

  • manager-script 能够访问文档中描述的适用于工具的纯文本界面,以及“服务器状态”页面。

  • manager-jmx 能够访问 JMX 代理界面以及“服务器状态”(Server Status)页面。

HTML 界面不会遭受 CSRF(Cross-Site Request Forgery,跨站请求伪造)攻击,但纯文本界面及 JMX 界面却有可能无法幸免。这意味着,如果用户能够访问纯文本界面及 JMX 界面,那么在利用 Web 浏览器去访问 Manager 应用时,必须要万分谨慎。要想保持对 CSRF 免疫,则必须:

在使用 Web 浏览器访问 Manager 应用时,假如用户具有 manager-script 或 manager-jmx 角色(比如为了测试纯文本界面或 JMX 界面),那么必须关闭所有的浏览器窗口,终止会话。如果不关闭浏览器,访问了其他站点,就可能会遭受 CSRF 攻击。

建议永远不要将 manager-script 或 manager-jmx 角色授予那些拥有 manager-gui 角色的用户。

注意 JMX 代理界面是 Tomcat 中非常高效的底层、类似于根级别的管理界面。如果用户知道了该调用的命令,就能实现大量行为,所以一定不要轻易授予用户 manager-jmx 角色。

为了能够访问 Manager 应用,必须创建一个新的用户名/密码组合,并为之授予一种 manager-** 角色,或者把一种 manager-** 角色授予现有的用户名/密码组合。因为本文档的大部分内容都在描述纯文本界面的命令,所以为了将来讨论实例方便起见,把角色名称定为 manager-script。而涉及到具体如何配置用户名及密码,则是跟你所使用的Realm 实现有关:

  • UserDatabaseRealm 加上 MemoryUserDatabase 或 MemoryRealm——UserDatabaseRealm 和 MemoryUserDatabase 配置在默认的 $CATALINA_BASE/conf/server.xml 文件中。MemoryUserDatabase 和 MemoryRealm 都会读取储存在 CATALINA_BASE/conf/tomcat-users.xml 里的 XML 格式文件,它可以用任何文本编辑器进行编辑。该文件会为每个用户定义一个 XML 格式的 <user> ,如下所示:

    <user username="craigmcc" password="secret" roles="standard,manager-script" />

    它定义了用户登录时所用的用户名和密码,以及他或她采用的角色名称。你可以把 manager-script 角色添加到由逗号分隔的 roles 属性中,从而将该角色赋予一个或多个用户,也可以利用指定角色来创建新的用户。

  • DataSourceRealm 或 JDBCRealm——用户和角色信息都存储在一个经由 JDBC 访问的数据库中。按照当前环境的标准流程,将 manager-script 角色赋予一个或多个用户,或者利用该角色创建一个或多个新用户。

  • JNDIRealm——你的用户和角色信息被存储在经由 LDAP 访问的一个目录服务器中。按照当前环境的标准流程,为一个或更多的现有用户添加 manager-script 角色,和(/或)利用指定角色创建一个或更多的新用户。

在下一节,当你第一次尝试使用 Manager 的一个命令时,将会使用基本验证进行登录。用户名和密码的具体内容并不重要,只要它们能够证明,用户数据库中拥有 manager-script 角色的用户是有效用户,我们的目的就达到了。


除了密码限制访问之外,Manager 还可以配置 RemoteAddrValve 和 RemoteHostValve 这两个参数,分别通过 远程 IP 地址 或远程主机名来进行限制访问。详情可查看 Valve 文档。下列范例是通过 IP 地址来限制访问本地主机:

<Context privileged="true">
         <Valve className="org.apache.catalina.valves.RemoteAddrValve"
                allow="127\.0\.0\.1"/>
</Context>

易用的 HTML 界面

Manager 应用易用的 HTML 界面位于:

http://{host}:{port}/manager/html

正像前面讲过的那样,需要被授予 manager-gui 角色才能访问它。关于这个界面,还有一个独立的文档,请访问以下页面:

Manager 的 HTML 界面文档

HTML 界面可免受 CSRF(跨站请求伪造)攻击。对 HTML 页面的每次访问都会生成一个随机令牌,存储在会话中,包含在页面的所有链接中。如果你的下一个操作没有正确的令牌值,操作就会被拒绝。如果令牌过期,可以从主页或者 Manager 的 List Applications(列出的应用)页面重新开始。


Manager 支持的命令

Manager 应用能够处理的命令都是通过下面这样的请求 URL 来指定的:

http://{host}:{port}/manager/text/{command}?{parameters}

{host} 和 {port} 分别代表运行 Tomcat 服务器所在的主机名和端口号。{command} 代表所要执行的 Manager 命令。{parameters} 代表该命令所专有的查询参数。在后面的实例中,可以为你的安装自定义适当的主机和端口。

这些命令通常是被 HTTP GET 请求执行的。/deploy 命令有一种能够被 HTTP PUT 请求所执行的形式。

常见参数

多数 Manager 命令都能够接受一个或多个查询参数,这些查询参数如下所示:

  • path 要处理的 Web 应用的上下文路径(包含前面的斜杠)。要想选择 ROOT Web 应用,指定 / 即可。注意:无法对 Manager 应用自身执行管理命令。

  • version StoreConfigLifecycleListener来配置。

    如果命令不能成功执行,响应将以 FAIL 开头,并包含一个错误消息。

    服务器状态

    可从下面这些链接中观察有关服务器的状态信息。任何一个 manager-** 角色都能访问这一页面。

    http://localhost:8080/manager/status
    http://localhost:8080/manager/status/all

    上面是用 HTML 格式显示服务器状态信息的命令。

    http://localhost:8080/manager/status?XML=true
    http://localhost:8080/manager/status/all?XML=true

    上面是用 XML 格式显示服务器状态信息的命令。

    首先,显示的是服务器和 JVM 的版本号、JVM 提供者、操作系统的名称及其版本号,然后还显示了系统体系架构类型。

    其次,显示的是关于 JVM 的内存使用信息。

    最后,显示的是关于 Tomcat AJP 和 HTTP 连接器的信息。对两者来说,这些信息都很有用:

    使用 /status/all 命令可查看每一个已配置 Web 应用的额外信息。

    使用 JMX 代理 Servlet

    什么是 JMX 代理 Servlet

    JMX 代理 Servlet 是一款轻量级的代理。它的用途对用户来说并不是特别友好,但是其 UI 却非常有助于整合命令行脚本,从便于监控和改变 Tomcat 的内部运行。通过这个代理,我们可以获取和设置信息。要想真正了解 JMX 代理 Servlet,首先应该大概了解 JMX。如果不知道 JMX 的基本原理,那有些内容就很难理解了。

    JMX 查询命令

    JMX 的查询命令格式如下所示:

    http://webserver/manager/jmxproxy/?qry=STUFF

    STUFF 是所要执行的 JMX 查询。比如,可以执行以下这些查询:

    需要实际地试验一下才能真正理解这些功能。如果没有提供 qry 参数,则将显示全部的 MBean。我们强烈建议你去阅读 Tomcat 源代码,真正了解 JMX 规范,更好地掌握所有能够执行的查询。

    JMX 的 get 命令

    JMXProxyServlet 还支持一种 get 命令来获取特定 MBean的属性值。该命令的一般格式如下所示:

    http://webserver/manager/jmxproxy/?get=BEANNAME&att=MYATTRIBUTE&key=MYKEY

    必须提供如下参数:

    如果命令成功执行,则一切正常,否则就会返回一个出错消息。举两个例子,比如当希望获取当前的堆内存数据时,可以采用如下命令:

    http://webserver/manager/jmxproxy/?get=java.lang:type=Memory&att=HeapMemoryUsage

    再或者,如果只希望获取“用过的”键,可以采用如下命令:

    http://webserver/manager/jmxproxy/?get=java.lang:type=Memory&att=HeapMemoryUsage&key=used

    JMX 的 set 命令

    上面介绍了如何查询一个 MBean。下面来看看 Tomcat 的内部运行吧!set 命令的一般格式为:

    http://webserver/manager/jmxproxy/?set=BEANNAME&att=MYATTRIBUTE&val=NEWVALUE

    需要提供三个请求参数:

    如果命令成功执行,则一切正常,否则就会返回一个出错消息。比如,假如想为 ErrorReportValve 进行立即调试,可以将属性 debug 设为 10:

    http://localhost:8080/manager/jmxproxy/
    ?set=Catalina%3Atype%3DValve%2Cname%3DErrorReportValve%2Chost%3Dlocalhost
    &att=debug&val=10  

    所得结果如下(你的有可能不同):

    Result: ok

    下面来看看如果传入一个不恰当数值时的情况,比如使用一个URL,并试图将属性 debug 设置为 'cow'。

    http://localhost:8080/manager/jmxproxy/
    ?set=Catalina%3Atype%3DValve%2Cname%3DErrorReportValve%2Chost%3Dlocalhost
    &att=debug&val=cow

    运行结果如下:

    Error: java.lang.NumberFormatException: For input string: "cow"

    JMX 的 invoke 命令

    使用 invoke 命令,我们就可以在 MBean 中调用方法。该命令的一般格式为:

    http://webserver/manager/jmxproxy/
    ?invoke=BEANNAME&op=METHODNAME&ps=COMMASEPARATEDPARAMETERS

    比如,使用如下方式来调用 Service 的 findConnectors() 方法:

    http://localhost:8080/manager/jmxproxy/
    ?invoke=Catalina%3Atype%3DService&op=findConnectors&ps=

    利用 Ant 执行 Manager 的命令

    上面的文档介绍了如何利用 HTTP 请求来执行 Manager 的命令。除此之外,Tomcat 还专为 Ant(1.4 版或更新版本)构建工具准备了一套方便的任务定义。为了使用这些命令,必须执行下面这些操作:

    为了在 Ant 中使用自定义任务,必须首先用 <taskdef> 元素来声明它们,因而 build.xml 文件应类似如下这样:

    <project name="My Application" default="compile" basedir=".">

     <!-- Configure the directory into which the web application is built -->
     <property name="build"    value="${basedir}/build"/>

     <!-- Configure the context path for this application -->
     <property name="path"     value="/myapp"/>

     <!-- Configure properties to access the Manager application -->
     <property name="url"      value="http://localhost:8080/manager/text"/>
     <property name="username" value="myusername"/>
     <property name="password" value="mypassword"/>

     <!-- Configure the custom Ant tasks for the Manager application -->
     <taskdef name="list"      classname="org.apache.catalina.ant.ListTask"/>
     <taskdef name="deploy"    classname="org.apache.catalina.ant.DeployTask"/>
     <taskdef name="start"     classname="org.apache.catalina.ant.StartTask"/>
     <taskdef name="reload"    classname="org.apache.catalina.ant.ReloadTask"/>
     <taskdef name="stop"      classname="org.apache.catalina.ant.StopTask"/>
     <taskdef name="undeploy"  classname="org.apache.catalina.ant.UndeployTask"/>
     <taskdef name="resources" classname="org.apache.catalina.ant.ResourcesTask"/>
     <typedef name="sessions"  classname="org.apache.catalina.ant.SessionsTask"/>
     <taskdef name="findleaks" classname="org.apache.catalina.ant.FindLeaksTask"/>
     <typedef name="vminfo"    classname="org.apache.catalina.ant.VminfoTask"/>
     <typedef name="threaddump" classname="org.apache.catalina.ant.ThreaddumpTask"/>
     <typedef name="sslConnectorCiphers" classname="org.apache.catalina.ant.SslConnectorCiphersTask"/>

     <!-- Executable Targets -->
     <target name="compile" description="Compile web application">
       <!-- ... construct web application in ${build} subdirectory, and
               generated a ${path}.war ... -->
     </target>

     <target name="deploy" description="Install web application"
             depends="compile">
       <deploy url="${url}" username="${username}" password="${password}"
               path="${path}" war="file:${build}${path}.war"/>
     </target>

     <target name="reload" description="Reload web application"
             depends="compile">
       <reload  url="${url}" username="${username}" password="${password}"
               path="${path}"/>
     </target>

     <target name="undeploy" description="Remove web application">
       <undeploy url="${url}" username="${username}" password="${password}"
               path="${path}"/>
     </target>

    </project>

    注意:上面的资源任务定义将覆盖 Ant 1.7 中所添加的资源数据类型。如果你希望使用这些资源数据类型,需要使用 Ant 命名空间支持,将 Tomcat 的任务分配到它们自己的命名空间中。

    现在,可以执行类似 ant deploy 这样的命令将应用部署到 Tomcat 的一个运行实例上,或者利用 ant reload 通知 Tomcat 重新加载应用。另外还需注意的是,在这个 build.xml 文件中,多数比较有价值的属性值都是可以被可替换的,因而可以利用命令行方式来重写这些值。比如,考虑到在 build.xml 文件中包含真正的管理员密码是非常危险的,可以通过一些命令来忽略密码属性,如下所示:

    ant -Dpassword=secret deploy

    • 下载 Ant 二进制分发包,地址为:http://ant.apache.org。必须使用 1.4 版本或更新版本。

    • 将分发包安装到合适的目录中(下面将把它叫做 ANT_HOME)。

    • 将文件 server/lib/catalina-ant.jar 从 Tomcat 安装目录中复制到 Ant 的库目录($ANT_HOME/lib)。

    • 将 $ANT_HOME/bin 目录添加到环境变量 PATH 中。

    • 在 Tomcat 用户数据库中,至少配置一个拥有 manager-script 角色的用户名/密码组合数据。

    • set:完整的 bean 名称。

    • att:想要改变的属性。

    • val:新的属性值。

    • qry=*%3Atype%3DRequestProcessor%2C* --> type=RequestProcessor 定位所有能够处理请求并汇报各自状态的 Worker。

    • qry=*%3Aj2eeType=Servlet%2c* --> j2eeType=Servlet 查询返回所有加载的 Servlet。

    • qry=Catalina%3Atype%3DEnvironment%2Cresourcetype%3DGlobal%2Cname%3DsimpleValue --> Catalina:type=Environment,resourcetype=Global,name=simpleValue 按照指定名称查找 MBean。

    • 解析及准备请求 将对请求报头进行解析,或进行必要的准备,以便读取请求主体(如果指定了传输编码)。

    • 服务 线程处理请求并生成响应。该阶段中至少有一个线程(可查看服务器状态页)。

    • 完成 请求处理结束。所有仍在输出缓冲区中的剩余响应都被传送至客户端。如果有必要保持连接活跃,则下一个阶段是“持续活跃”阶段,否则接下来直接进入“就绪”阶段。

    • 持续活跃 当客户端发送另一请求时,线程能使连接对客户端保持开放。如果接收到另一请求,下一阶段就将是“解析及准备请求”阶段。如果持续活跃超时结束,仍没有接收到请求,则连接关闭,进入下一阶段“就绪”阶段。

    • 就绪 线程空闲,等待再此被使用。

    • 线程信息:最大线程数、最少及最多的空闲线程数、当前线程数量以及当前繁忙线程。

    • 请求信息:最长及最短的处理时间、请求和错误的数量,以及接受和发送的字节数量。

    • 一张完整显示线程阶段、时间、发送字节数、接受字节数、客户端、虚拟主机及请求的表。它将列出所有现有线程。下面列出了所有可能的线程阶段:

    1. get:MBean 的完整名称。

    2. att:希望获取的属性。

    3. key:(可选参数)CompositeData MBean 的属性中的键。

任务输出捕获

使用 Ant 1.6.2 版或更新版本,Catalina 任务提供选项,利用属性或外部文件捕获输出。它们直接支持 <redirector> 类型属性的子集:

属性属性说明是否必需
output输出文件名。如果错误流没有重定向到一个文件或属性上,它将出现在输出中。
error命令的标准错误应该被重定向到的文件。
logError用于在 Ant 日志中显示错误输出,将输出重定向至某个文件或属性。错误输出不会包含在输出文件或属性中。如果利用 error 或 errorProperty属性重定向错误,则没有任何效果。
append输出和错误文件是否应该附加或覆盖。默认为 false
createemptyfiles是否应该创建输出和错误文件,哪怕是空的文件。默认为 true
outputproperty用于保存命令输出的属性名。除非错误流被重定向至单独的文件或流,否则这一属性将包含错误输出。
errorproperty用于保存命令标准错误的属性名。

还可以指定其他一些额外属性:

属性属性说明是否必需
alwaysLog该属性用于查看捕获的输出,这个输出也出现在 Ant 日志中。除非捕获任务输出,否则千万不要使用它。默认为 falseAnt 1.6.3 通过 <redirector>直接支持该属性。
failonerror用于避免因为 manager 命令处理中错误而导致 Ant 执行终止情况的发生。默认为 true。如果希望捕获错误输出,则必须设为false,否则 Ant 执行将有可能在未捕获任何输出前就被终止。该属性只用于 manager 命令的执行上,任何错误的或丢失的命令属性仍然会导致 Ant 执行终止。

它们还支持内嵌的 <redirector> 元素,你可以在这些元素中指定全套的属性。但对于input、inputstring、inputencoding,即使接收,也无法使用,因为在这种上下文中它们没有任何意义。详情可参考 Ant 手册以了解 <redirector> 元素的各个属性。


下面这个范例摘录了一段构建文件,展示了这种对输出重定向的支持是如何运作的。

 <target name="manager.deploy"
        depends="context.status"
        if="context.notInstalled">
        <deploy url="${mgr.url}"
            username="${mgr.username}"
            password="${mgr.password}"
            path="${mgr.context.path}"
            config="${mgr.context.descriptor}"/>
    </target>

    <target name="manager.deploy.war"
        depends="context.status"
        if="context.deployable">
        <deploy url="${mgr.url}"
            username="${mgr.username}"
            password="${mgr.password}"
            update="${mgr.update}"
            path="${mgr.context.path}"
            war="${mgr.war.file}"/>
    </target>

    <target name="context.status">
        <property name="running" value="${mgr.context.path}:running"/>
        <property name="stopped" value="${mgr.context.path}:stopped"/>

        <list url="${mgr.url}"
            outputproperty="ctx.status"
            username="${mgr.username}"
            password="${mgr.password}">
        </list>

        <condition property="context.running">
            <contains string="${ctx.status}" substring="${running}"/>
        </condition>
        <condition property="context.stopped">
            <contains string="${ctx.status}" substring="${stopped}"/>
        </condition>
        <condition property="context.notInstalled">
            <and>
                <isfalse value="${context.running}"/>
                <isfalse value="${context.stopped}"/>
            </and>
        </condition>
        <condition property="context.deployable">
            <or>
                <istrue value="${context.notInstalled}"/>
                <and>
                    <istrue value="${context.running}"/>
                    <istrue value="${mgr.update}"/>
                </and>
                <and>
                    <istrue value="${context.stopped}"/>
                    <istrue value="${mgr.update}"/>
                </and>
            </or>
        </condition>
        <condition property="context.undeployable">
            <or>
                <istrue value="${context.running}"/>
                <istrue value="${context.stopped}"/>
            </or>
        </condition>
    </target>

警告:多次调用 Catalina 任务往往并不是一个好主意,退一步说这样做的意义也不是很大。如果 Ant 任务依赖链设定糟糕的话,即使本意并非如此,也会导致在一次 Ant 运行中多次运行任务。必须提前对你稍加警告,因为有可能当你从任务中捕获输出时,会出现一些意想不到的情况:

当用属性捕获时,你将只能从其中找到最初调用的输出,因为 Ant 属性是不变的,一旦设定就无法改变。

当用文件捕获时,你将只能从其中找到最后调用的输出,除非使用 append = "true" 属性——在这种情况下,你将看到附加在文件内容末尾的每一个任务调用的相关输出。



小说《我是全球混乱的源头》

感觉本站内容不错,读后有收获?小额赞助,鼓励网站分享出更好的教程