我有一个带有@WebServlet
注释类的java代理。我通过在-javaagent:/path/to/agent/jar
中指定JAVA_OPTS
,使用Servlet 3.0将此代理附加到基于servlet的应用程序。
然而,servlet似乎没有被加载,并且在尝试访问servlet时出现404错误。
这甚至可能吗?
答案 0 :(得分:2)
TLDR:https://github.com/tsabirgaliev/tomcat-agent
public class Agent {
public static class Target {
public static final String GLOBAL_SERVLET_PATTERN = "/globalServlet";
public static final String GLOBAL_SERVLET_NAME = "globalServlet";
public boolean intercept(
@SuperCall Callable<Boolean> zuper
, @Argument(0) InputSource source
, @Argument(1) Object dest
, @Argument(2) boolean fragment
, @This Object self
) {
try {
boolean ok = zuper.call();
if (!fragment) {
Method getServletMappings = dest.getClass().getMethod("getServletMappings");
Map<String, String> mappings = (Map<String, String>)getServletMappings.invoke(dest);
if (!mappings.containsKey(GLOBAL_SERVLET_PATTERN)) {
ClassLoader loader = self.getClass().getClassLoader();
Class<?> servletDefClass = Class
.forName("org.apache.tomcat.util.descriptor.web.ServletDef", true, loader);
Object servletDef = servletDefClass.newInstance();
servletDefClass.getMethod("setServletClass", String.class)
.invoke(servletDef, "io.tair.myagent.GlobalServlet");
servletDefClass.getMethod("setServletName", String.class)
.invoke(servletDef, GLOBAL_SERVLET_NAME);
dest.getClass().getMethod("addServlet", servletDefClass)
.invoke(dest, servletDef);
dest.getClass().getMethod("addServletMapping", String.class, String.class)
.invoke(dest, GLOBAL_SERVLET_PATTERN, GLOBAL_SERVLET_NAME);
}
}
return ok;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
public static void premain(String agentArgs, Instrumentation inst) {
new AgentBuilder.Default()
.type(named("org.apache.tomcat.util.descriptor.web.WebXmlParser"))
.transform((builder, typeDescription, classLoader) ->
builder.method (
named("parseWebXml")
.and(takesArgument(0, InputSource.class))
)
.intercept(MethodDelegation.to(new Target()))
)
.installOn(inst);
}
}
我们的想法是拦截对org.apache...WebXmlParser#parseWebXml(InputSource, WebXml, boolean)
的调用,并在解析web.xml文件后立即添加必要的servlet映射。
重要的部分由出色的ByteBuddy [1]处理。要使此代理工作,您必须包含ByteBuddy类。
如果GlobalServlet打包在代理中,您还必须包含ServletApi类。这肯定可以避免,但我不知道足够的ByteBuffer魔法来实现它。
除了servlet-api之外,以下解决方案还需要jsp-api。
public class Agent {
public static class Target {
public static final String GLOBAL_SERVLET_PATTERN = "/globalServlet";
public static final String GLOBAL_SERVLET_NAME = "globalServlet";
public void intercept(
@SuperCall Callable<Void> zuper
, @This Object self
) {
try {
Method getServletMappings = self.getClass().getMethod("getServletMappings");
Map<String, String> mappings = (Map<String, String>)getServletMappings.invoke(self);
if (!mappings.containsKey(GLOBAL_SERVLET_PATTERN)) {
ClassLoader loader = self.getClass().getClassLoader();
Class<?> servletDefClass = Class
.forName("org.apache.catalina.deploy.ServletDef", true, loader);
Object servletDef = servletDefClass.newInstance();
servletDefClass.getMethod("setServletClass", String.class)
.invoke(servletDef, "io.tair.myagent.GlobalServlet");
servletDefClass.getMethod("setServletName", String.class)
.invoke(servletDef, GLOBAL_SERVLET_NAME);
self.getClass().getMethod("addServlet", servletDefClass)
.invoke(self, servletDef);
self.getClass().getMethod("addServletMapping", String.class, String.class)
.invoke(self, GLOBAL_SERVLET_PATTERN, GLOBAL_SERVLET_NAME);
}
zuper.call();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
public static void premain(String agentArgs, Instrumentation inst) {
new AgentBuilder.Default()
.type(named("org.apache.catalina.deploy.WebXml"))
.transform((builder, typeDescription, classLoader) ->
builder.method (
named("configureContext")
)
.intercept(MethodDelegation.to(new Target()))
)
.installOn(inst);
}
}