Tomcat 5.5.26 Administration Tool HTTP Status 500 解决前后

来源:互联网 发布:2016网络歌手排行榜 编辑:程序博客网 时间:2024/05/16 00:58
p { text-indent: 2em; margin-bottom: 1em; margin-top: 1em; }.codeseg_caption { font-weight: bold; line-height: 2em; }#stepbystep ol { line-height: 1.5em; }

闲话少说,如果您也在使用 Tomcat 5.5.26 Administration Tool 的 Delete Existing Hosts 功能时遇到如下问题,本文至少能提供一种有效的解决方法(贴上错误信息文本,一是给搜索引擎预备的,二来也帮助您确认一下本文是否对症)。

Delete Existing Hosts

HTTP Status 500 -

--------------------------------------------------------------------------------

type Exception report

message

description The server encountered an internal error () that prevented it from fulfilling this request.

exception

java.lang.NullPointerException
    java.lang.String.indexOf(Unknown Source)
    java.lang.String.indexOf(Unknown Source)
    org.apache.struts.taglib.logic.MatchTag.condition(MatchTag.java:158)
    org.apache.struts.taglib.logic.MatchTag.condition(MatchTag.java:100)
    org.apache.struts.taglib.logic.ConditionalTagBase.doStartTag(ConditionalTagBase.java:174)
    admin.host.hosts_jsp._jspService(hosts_jsp.java:178)
    org.apache.jasper.runtime.HttpJspBase.service(HttpJspBase.java:98)
    javax.servlet.http.HttpServlet.service(HttpServlet.java:803)
    org.apache.struts.action.RequestProcessor.doForward(RequestProcessor.java:1085)
    org.apache.struts.action.RequestProcessor.processForwardConfig(RequestProcessor.java:398)
    org.apache.struts.action.RequestProcessor.process(RequestProcessor.java:241)
    org.apache.struts.action.ActionServlet.process(ActionServlet.java:1196)
    org.apache.struts.action.ActionServlet.doGet(ActionServlet.java:414)
    javax.servlet.http.HttpServlet.service(HttpServlet.java:690)
    javax.servlet.http.HttpServlet.service(HttpServlet.java:803)
    org.apache.webapp.admin.filters.SetCharacterEncodingFilter.doFilter(SetCharacterEncodingFilter.java:123)


note The full stack trace of the root cause is available in the Apache Tomcat/5.5.26 logs.


--------------------------------------------------------------------------------

Apache Tomcat/5.5.26

如果您只想解决问题,也就是顺利使用 Delete Exsiting Hosts 功能,那么您只需:

  1. 浏览 http://tomcat.apache.org/download-55.cgi,在 Source Code Distributions 下载源代码压缩包(条件允许的话,最好借助 md5 等方式检查一下文件完整性);
  2. 解压缩下载的源代码压缩包,找到 apache-tomcat-5.5.26-src/container/webapps/admin/WEB-INF/classes/org/apache/webapp/admin/host/DeleteHostAction.java 的第 116 行,将 "domain" 两侧的一对双引号删除;
  3. 使用您熟悉的方式,重新编译该文件,获得 DeleteHostAction.class 或者 jar 文件(我编译好的 DeleteHostAction.class 可以在 CSDN 下载频道 获取);
  4. 将编译好的文件部署到服务器上的正确位置,%CATALINA_HOME%/server/webapps/admin/WEB-INF/classes/org/apache/webapp/admin/host/DeleteHostAction.class 或者 %CATALINA_HOME%/server/webapps/admin/WEB-INF/lib/catalina-admin.jar,二者必取其一。我采取的方法是重命名 %CATALINA_HOME%/server/webapps/admin/WEB-INF/lib/catalina-admin.jar 把她屏蔽掉,然后把其内容解压缩到 %CATALINA_HOME%/server/webapps/admin/WEB-INF/classes/,这样就可以直接拿 DeletaHostAction.class 文件去替换掉原来的那个,一是怕还有别的 .class 需要改,二是图省事儿,就没用 jar 的方式。

下面就是本人发现这一解决方法的始末。事先声明,本人一直走 ASP.NET 路线,对 Java 及相关技术知之甚少,如有疏漏还请指正,互相学习共同提高。

从上述调用栈我们可以看出,问题出在 admin.host.hosts_jsp._jspService 这个方法中,在源文件 hosts_jsp.java 的 178 行,好,那就开始寻找源文件。费了一番周折才发现,原来 Administration Tool 的源代码都在 Tomcat 5.5.26 的 src 包里:apache-tomcat-5.5.26-src.tar.gz 或 .zip(http://tomcat.apache.org/download-55.cgi)。压缩包中的具体位置是 apache-tomcat-5.5.26-src/container/webapps/admin/host/hosts.jsp。

这中间还有一个插曲,我估计是出于性能考虑,官方发布的这个 Admin Web Application 是把 JSP 编译好了当 Servlet 使,因为发现 %CATALINA_HOME%/server/webapps/admin/WEB-INF/web.xml 中有 <servlet-mapping /> 配置,把对 JSP 的请求交给编译好的形如 jspname_jsp.class 的 Servlet 来处理。在找到官方发布的源代码之前,我曾经带着学习的目的反编译了那个 admin.host.hosts_jsp.class(位于 %CATALINA_HOME%/server/webapps/admin/WEB-INF/lib/catalina-admin.jar),其内容真是惨不忍睹啊……

我们还是看看正儿八经的源文件吧,hosts.jsp 中有如下一段:

文件片断:apache-tomcat-5.5.26-src/container/webapps/admin/host/hosts.jsp
  1.              <logic:match name="host"
  2.                         value='<%= (String)request.getAttribute("adminAppHost") %>'>
  3.              <font color='red'>*</font>
  4.              </logic:match>
  5.              <logic:notMatch name="host"
  6.                         value='<%= (String)request.getAttribute("adminAppHost") %>'>
  7.               <label for="hosts"></label>         
  8.               <html:multibox property="hosts"
  9.                                 value="<%= host.toString() %>" styleId="hosts"/>
  10.               </logic:notMatch>

看到这里我就确信,报异常的就是这个地方:既有 <logic:match /> 和 <logic:nomatch /> 标记(尽管不知道她如何起作用,但语义还是能看出来的),又有两个将某种东西强制转换成 String 的地方(第 78 和 82 行)。

这段源代码和最早遇到的调用栈告诉我,request.getAttribute("adminAppHost") 得到了 null。我就很好奇,到底她当初是怎么 setAttribute("adminAppHost", value) 的呢?这次就要从页面入手了。登录 Administration Tool,右键右下角的 frame 查看源文件:

代码片断:EditService[1]
  1. <select id="labelId" onchange="IA_jumpMenu('self',this)">
  2. <option selected="selected" value="">-----Available Actions-----</option>
  3. <option disabled="true" value="">-------------------------------------</option>
  4. <option value="/admin/AddConnector.do?select=Catalina%3Atype%3DService%2CserviceName%3DCatalina">Create New Connector</option>
  5. <option value="/admin/DeleteConnector.do?select=Catalina%3Atype%3DService%2CserviceName%3DCatalina">Delete Existing Connectors</option>
  6. <option value="">-------------------------------------</option>
  7. <option disabled="true" value="">-------------------------------------</option>
  8. <option value="/admin/AddHost.do?select=Catalina%3Atype%3DService%2CserviceName%3DCatalina">Create New Host</option>
  9. <option value="/admin/DeleteHost.do?select=Catalina%3Atype%3DService%2CserviceName%3DCatalina">Delete Existing Hosts</option>
  10. <option disabled="true" value="">-------------------------------------</option>
  11. <option disabled="true" value="">-------------------------------------</option>
  12. <option value="/admin/AddValve.do?parent=Catalina%3Atype%3DService%2CserviceName%3DCatalina">Create New Valve</option>
  13. <option value="/admin/DeleteValve.do?parent=Catalina%3Atype%3DService%2CserviceName%3DCatalina">Delete Existing Valves</option>
  14. </select>

只看第 103 行即可,她是说,选择“Delete Existing Hosts”下拉列表项之后,发生了 DeleteHost.do(还带了乱七八糟的参数,%3A:%3D=%2C,),托 %CATALINA_HOME%/server/webapps/admin/WEB-INF/struts-config.xml 的福,找到了 DeleteHost.do 的责任者:

文件片断:%CATALINA_HOME%/server/webapps/admin/WEB-INF/struts-config.xml
  1.     <!-- Set up Delete Hosts transaction -->
  2.     <action    path="/DeleteHost"
  3.                type="org.apache.webapp.admin.host.DeleteHostAction"
  4.                name="hostsForm"
  5.                scope="request"/>

于是去查看 apache-tomcat-5.5.26-src/container/webapps/admin/WEB-INF/classes/org/apache/webapp/admin/host/DeleteHostAction.java 的内容。果不其然,这里有 setAttribute:

文件片断:apache-tomcat-5.5.26-src/container/webapps/admin/web-inf/classes/org/apache/webapp/admin/host/deletehostaction.java
  1.         // Set up a form bean containing the currently selected
  2.         // objects to be deleted
  3.         HostsForm hostsForm = new HostsForm();
  4.         String select = request.getParameter("select");
  5.         String domain = null;
  6.         if (select != null) {
  7.             String hosts[] = new String[1];
  8.             hosts[0] = select;
  9.             hostsForm.setHosts(hosts);
  10.                         
  11.             try {
  12.                 domain = (new ObjectName(select)).getDomain();
  13.             } catch (Exception e) {
  14.                 throw new ServletException
  15.                 ("Error extracting service name from the host to be deleted", e);
  16.             }        
  17.         }
  18.         String adminHost = null;
  19.         // Get the host name the admin app runs on
  20.         // this host cannot be deleted from the admin tool
  21.         try {
  22.             adminHost = Lists.getAdminAppHost(
  23.                                   mBServer, "domain" ,request);
  24.         } catch (Exception e) {
  25.             String message =
  26.                 resources.getMessage(locale, "error.hostName.bad",
  27.                                         adminHost);
  28.             getServlet().log(message);
  29.             response.sendError(HttpServletResponse.SC_BAD_REQUEST, message);
  30.             return (null);
  31.         }
  32.         request.setAttribute("adminAppHost", adminHost);       
  33.         request.setAttribute("hostsForm", hostsForm);

代码截取的比较多,先看第 98 和 105 行,知道这个作用域内有个局部变量叫 domain 就行了。然后看第 111 和 115 行,还有第 125 行。好了,现在问题集中在了 Lists.getAdminAppHost() 上。不出意外(也就是不抛异常)的话,显然 Lists.getAdminAppHost() 的返回值被 request.setAttribute() 设置成了 adminAppHost 这个 attribute 的取值。

那么我们来看看 Lists 她是怎么说的,找到源文件 apache-tomcat-5.5.26-src/container/webapps/admin/WEB-INF/classes/org/apache/webapp/admin/Lists.java:

文件片断:apache-tomcat-5.5.26-src/container/webapps/admin/WEB-INF/classes/org/apache/webapp/admin/Lists.java
  1.     /**
  2.      * Return the  <code>Host</code> object name string
  3.      * that the admin app belongs to.
  4.      *
  5.      * @param mbserver MBeanServer from which to retrieve the list
  6.      * @param request Http request
  7.      *
  8.      * @exception Exception if thrown while retrieving the list
  9.      */
  10.     public static String getAdminAppHost
  11.         (MBeanServer mbserver, String domain, HttpServletRequest request)
  12.         throws Exception {
  13.         
  14.         // Get the admin app's host name
  15.         StringBuffer sb = new StringBuffer(domain);
  16.         sb.append(":j2eeType=WebModule,*"); 
  17.         ObjectName search = new ObjectName(sb.toString());
  18.         Iterator names = mbserver.queryNames(search, null).iterator();
  19.         String contextPath = request.getContextPath();
  20.         String host = null;
  21.         String name = null;
  22.         ObjectName oname = null;
  23.         while (names.hasNext()) {       
  24.             name = names.next().toString();
  25.             oname = new ObjectName(name);
  26.             host = oname.getKeyProperty("name");
  27.             host = host.substring(2);
  28.             int i = host.indexOf("/");
  29.             if (contextPath.equals(host.substring(i))) {
  30.                 host = host.substring(0,i);
  31.                 return host;
  32.             }
  33.         }
  34.         return host;

  35.     }

简单来说,getAdminAppHost() 这个方法的第二个参数 domain 被用来寻找 names,而 names 中有我们需要的 host 的内容。经过调试,发现当 domain 取值为 "domain" 时,names.hasNext() 返回 false,host 即最终的返回值亦无可避免成为 null,直至导致 hosts.jsp 中 request.getAttribute("adminAppHost") 时得到 null。回过头来看看 DeleteHostAction.java 的第 115 和 116 行吧,第二个参数是常量字符串 "domain" 而不是局部变量 domain,当去掉这一对双引号,再重新编译后,问题看起来就解决了(见下图,逻辑很正确,Admin Tool 所在的 Host 不能删除)。

Delete This Host

现在唯一还困扰我的就是,官方发布的版本为什么会存在这个问题?我相信除了遇到相同问题的我们,有更多的人没有遇到这一问题,尤其是在发现 %CATALINA_HOME%/server/webapps/admin/WEB-INF/web.xml 中的这句话之后,我被彻底打败了……

文件片断:%CATALINA_HOME%/server/webapps/admin/WEB-INF/web.xml
  1.     <init-param>
  2.       <param-name>domain</param-name>
  3.       <param-value>Catalina</param-value>
  4.     </init-param>