在 Tomcat 部署的应用里,我们一般意义上都是指普通的应用,大家可以执行的操作等都是一样的,一条起跑线上的,没有区别。
但对于一些特殊的应用,需要执行一些特别的操作,比如官方默认提供的 manager应用,可以进行应用的启动、停止、部署等一系列操作。这些操作的背后,是 Tomcat默认包含的 ManagerServlet 实现的, manager 应用实现的,是对于该 Servelet 的调用。
那我们自己写一个应用去调用这个 ManagerServlet 不也一样可以吗?
答案是不行的,至少直接操作不行,需要配置。这里就是我们今天提到的 Tomcat 内的特权应用(privileged app)。
manager 应用就是自带的一个特权应用,因此它可以直接调用 ManagerServlet 进行一些容器内部的操作。
我们单独部署的其他应用,就会被限制调用这些容器提供的内部组件,比如一些 Servlet、Filter、Listener。
这些限制资源进行检查时,分为两种类型。
一类是通过类型来进行判断,比如 Servlet 中,ManagerServlet、HTMLManagerServlet 这些都被称为 ContainerServlet。实现了相同的接口。
还有一类是在配置文件中指定的,有三个文件:
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);
}
clazz = clazz.getSuperclass();
}
}
对于 priviledged 应用,之后的 checkAccess 就直接跳过,否则会判断是否是 ContainerServlet,是否在配置文件中进行限制等,只有都检查通过才可以进行 使用。
例如 普通的应用在使用HTMLManagerServlet ,此时由于限制检查,会提示500。
所以,之后有需要使用容器提供的功能时,可以将应用升级为特权应用,然后调用容器提供的高级功能。
相关阅读
Tomcat目录部署与Context描述文件context.xml
☆★☆更多精彩内容☆★☆
一台机器上安装多个Tomcat 的原理(回复001)
监控Tomcat中的各种数据 (回复002)
启动Tomcat的安全机制(回复003)
乱码问题的原理及解决方式(回复007)
Tomcat 日志工作原理及配置(回复011)
web.xml 解析实现(回复 012)
线程池的原理( 回复 014)
Tomcat 的集群搭建原理与实现 (回复 015)
类加载器的原理 (回复 016)
类找不到等问题 (回复 017)
代码的热替换实现(回复 018)
Tomcat 进程自动退出问题 (回复 019)
为什么总是返回404? (回复 020)
...
如有帮助,一起喝一杯吧!
PS: 对于一些 Tomcat常见问题,在公众号的【常见问题】菜单中,有需要的朋友欢迎关注查看。
觉得本文对你有帮助?请分享给更多人支持一下吧,谢谢
关注『 Tomcat那些事儿 』 ,发现更多精彩文章!了解各种常见问题背后的原理与答案。深入源码,分析细节,内容原创,欢迎关注。