在 Tomcat 部署的应用里,我们一般意义上都是指普通的应用,大家可以执行的操作等都是一样的,一条起跑线上的,没有区别。
但对于一些特殊的应用,需要执行一些特别的操作,比如官方默认提供的 manager应用,可以进行应用的启动、停止、部署等一系列操作。这些操作的背后,是 Tomcat默认包含的 ManagerServlet 实现的, manager 应用实现的,是对于该 Servelet 的调用。
那我们自己写一个应用去调用这个 ManagerServlet 不也一样可以吗?
答案是不行的,至少直接操作不行,需要配置。这里就是我们今天提到的 Tomcat 内的特权应用(privileged app)。
manager 应用就是自带的一个特权应用,因此它可以直接调用 ManagerServlet 进行一些容器内部的操作。
我们单独部署的其他应用,就会被限制调用这些容器提供的内部组件,比如一些 Servlet、Filter、Listener。
这些限制资源进行检查时,分为两种类型。
1. 一类是通过类型来进行判断,比如 Servlet 中,ManagerServlet、HTMLManagerServlet 这些都被称为ContainerServlet。实现了相同的接口。
2. 还有一类是在配置文件中指定的,有三个文件:
- RestrictedFilters.properties
- RestrictedListeners.properties
- RestrictedServlets.properties
位于 org.apache.catalina.core 包中。
这些内部在 DefaultInstanceManager 初始化时进行解析,例如 servlet的内容解析后是这些:
这些内容也是容器提供的一些功能的实现,比如 GCI 请求处理, JMXProxyServlet 会将 JMX 的属性 dump 出来等等。
特权应用的配置
那怎样能让单独开发的应用成为特权应用,能调用这些容器的功能呢?
之前的文章介绍过应用的部署描述文件context.xml ( Tomcat目录部署与Context描述文件context.xml),这次我们的升级特权操作也是需要在这里进行。
具体来说,在应用部署时,将应用的 Context 对象中 privileged属性设置为 true。例如 manager 应用的配置是这样的,文件位于于 manager 的META-INF 目录中,单独开发时可以参考:
- <Context antiResourceLocking="false" privileged="true" >
这样配置之后,在应用部署时就会将代表应用的 StandardContext 对象中 privileged 属性设置为true,后面会读取使用。
特权应用的工作原理
应用在启动时,会将 DefaultInstanceManager 做为其 instanceManager。该属性在后面 Servlet、Filter、Listener 这些组件加载时使用,做为实例管理器,进行 newInstance的管理。
以 Servlet 为例,有些 loadOnStartup 的 Servlet,会在部署启动应用时直接生成实例。
在 loadServlet 阶段时,会先通过 Wrapper 获取父容器 Context 的 instanceManager,再通过 instanceManager 来加载具体的 Servlet class。
以下为 instanceManager 进行 newInstace 时的逻辑
- public Object newInstance(String className) {
- Class<?> clazz = loadClassMaybePrivileged(className, classLoader);
- return newInstance(clazz.newInstance(), clazz);
- }
在loadClassMaybePrivileged中,对于catalina包中的 class,会使用ServerClassLoader 来进行加载,除了通过不同的classLoader加载外,还会进行上面说的 privileged 检查。
- private void checkAccess(Class<?> clazz) {
- if (privileged) {
- return;
- }
- if (Filter.class.isAssignableFrom(clazz)) {
- checkAccess(clazz, restrictedFilters);
- } else if (Servlet.class.isAssignableFrom(clazz)) {
- if (ContainerServlet.class.isAssignableFrom(clazz)) {
- throw new SecurityException("Restricted (ContainerServlet) " +
- clazz);
- }
- checkAccess(clazz, restrictedServlets);
- } else {
- checkAccess(clazz, restrictedListeners);
- }
- }
- // 这里检查配置文件中的内容
- private void checkAccess(Class<?> clazz, Properties restricted) {
- while (clazz != null) {
- if ("restricted".equals(restricted.getProperty(clazz.getName()))) {
- throw new SecurityException("Restricted " + clazz);
- }
- clazzclazz = clazz.getSuperclass();
- }
- }
对于 priviledged 应用,之后的 checkAccess 就直接跳过,否则会判断是否是 ContainerServlet,是否在配置文件中进行限制等,只有都检查通过才可以进行 使用。
例如 普通的应用在使用HTMLManagerServlet ,此时由于限制检查,会提示500。
所以,之后有需要使用容器提供的功能时,可以将应用升级为特权应用,然后调用容器提供的高级功能。
【本文为51CTO专栏作者“侯树成”的原创稿件,转载请通过作者微信公众号『Tomcat那些事儿』获取授权】