Loading...
墨滴

四千岁

2021/12/11  阅读:47  主题:默认主题

404页面是谁返回给浏览器的?

404
404

今天来看看 404 页面是谁返回的?

最近突然想到很久之前学过的关于Servlet的一个知识点:就是当一个请求没有对应的Servlet去处理时,就会交给Tomcat的org.apache.catalina.servlets.DefaultServlet 去处理,当org.apache.catalina.servlets.DefaultServlet也不知道怎么处理这个请求的时候就会抛出一个404错误。

1 先看DefaultServlet的简介吧

org.apache.catalina.servlets.DefaultServlet在Tomcat中是一个全局的Servlet,只要是在Tomcat下面部署的Web应用都会用到它。DefaultServlet是在Tomcat的安装目录conf下面的Web.xml里面配置的。如下截图:

Tomcat的conf目录
Tomcat的conf目录

打开这个Web.xml文件,如下截图:

DefaultServlet
DefaultServlet
DefaultServlet的请求映射
DefaultServlet的请求映射

再来看一下Web.xml文件里面关于DefaultServlet的介绍:

Web.xml文件里面关于DefaultServlet的介绍
Web.xml文件里面关于DefaultServlet的介绍

Tomcat的安装目录conf下面Web.xml文件里面关于DefaultServlet的介绍

The default servlet for all web applications, that serves static resources. It processes all requests that are not mapped to other servlets with servlet mappings (defined either here or in your own web.xml file. This servlet supports the following initialization parameters (default values are in square brackets):

翻译成中文就是: 这个Servlet默认是为所有的Web应用处理静态资源的。它可以处理所有找不到映射的请求(它可以定义在这里也可以定义在你项目里面的web.xml。这个servlet支持下面初始化参数,默认值在方括号中。)

注意这个DefaultServlet只能处理静态资源,比如css文件、图片文件、html文件。jsp文件DefaultServlet是处理不了的,jsp文件由专门的org.apache.jasper.servlet.JspServlet来处理。如果一个请求看不出来是动态的还是静态的,默认当作静态请求,交给DefaultServlet处理。

2 先看404页面长什么样子

看完了DefaultServlet的介绍,我们先来启动一个JavaWeb项目故意访问一个不存在的请求看一下。

404页面
404页面

可以看到tomcat给浏览器返回了一个404页面,并且这个页面的文字是中文的,看起来好像还有css样式。这个404的html页面是谁返回的?我们用Fiddler工具抓包看一下Tomcat返回给浏览器的到底是什么东西?

Fiddler抓包
Fiddler抓包

抓到的内容如下:

<!doctype html>
<html lang="zh">
 <head>
  <title>HTTP状态 404 - 未找到</title>
  <style type="text/css">
   body {font-family:Tahoma,Arial,sans-serif;} 
   h1, h2, h3, b {color:white;background-color:#525D76;} 
   h1 {font-size:22px;} h2 {font-size:16px;} 
   h3 {font-size:14px;} 
   p {font-size:12px;} 
   a {color:black;} 
   .line {height:1px;background-color:#525D76;border:none;}
  </style>
 </head>
 <body>
  <h1>HTTP状态 404 - 未找到</h1>
  <hr class="line" /><p><b>类型</b> 状态报告</p>
  <p><b>消息</b> 请求的资源[/JavaWeb/index1.image]不可用</p><p><b>描述</b> 源服务器未能找到目标资源的表示或者是不愿公开一个已经存在的资源表示。</p>
  <hr class="line" />
  <h3>Apache Tomcat/7.0.104</h3>
 </body>
</html>

可以看到Tomcat返回给浏览器的内容是一个html页面,并且这个页面上的字都是中文的。我们的JavaWeb应用并没有编写这样的html页面。看了上面关于DefaultServlet的介绍,我怀疑这个404页面是DefaultServlet返回的,我们来看一下DefaultServlet的源码。

3 DefaultServlet的源码

在Tomcat的安装目录lib文件下面有很多jar包,如下截图:

Tomcat的安装目录
Tomcat的安装目录

我们从DefaultServlet的完整包名org.apache.catalina.servlets.DefaultServlet猜测这个类应该在catalina.jar这个jar包里面。使用jd-gui反编译工具看一下catalina.jar包里面的代码:

DefaultServlet源码
DefaultServlet源码

可以看到DefaultServlet跟我们平时开发的Servlet是一样的,都是继承了HttpServlet。这就好办了,我们直接去找核心方法doGet、doPost、service这个三个方法。

service和doGet方法
service和doGet方法
doPost和doGet方法
doPost和doGet方法

大致看了一下源码,可以看到doPost方法里面调用的是doGet方法,doGet方法里面调用的是serveResource方法。service方法有俩个分支:一个是doGet方法,一个是调用父类(HttpServlet)的service方法。从源码可以看出来,不管是post请求还是get请求最终都会交给serveResource方法去处理,那么我们来看一下serveResource方法,如下截图:

serveResource方法
serveResource方法

注意看这行代码

response.sendError(404, sm.getString("defaultServlet.missingResource", new Object[] { requestUri }));

这行代码通过HttpServletResponse的sendError方法返回一个404错误。我们来看一下HttpServletResponse的源码,如下截图:

HttpServletResponse
HttpServletResponse
sendError方法
sendError方法

注意sendError方法有俩个参数,一个int类型,一个String类型。看到这里我怀疑刚才那个404页面上的中文就是通过这里传进去的。再回头看一下这行代码:

response.sendError(404, sm.getString("defaultServlet.missingResource", new Object[] { requestUri }));

这里面的sm.getString("defaultServlet.missingResource", new Object[] { requestUri })是什么东西?

看下源码:

sm.getString方法
sm.getString方法

还是一样从完整的包路径org.apache.tomcat.util.res.StringManager猜测出来这个StringManager类应该在tomcat-util.jar里面。

tomcat-util.jar
tomcat-util.jar

StringManager类的源码,如下:

StringManager类的源码
StringManager类的源码

当我看到Locale的时候,我已经明白为什么404页面上为啥有中文了。这不就是学JAVA时的国际化知识点吗?果然,我在Tomcat的安装目录下面找到了i18n-zh-CN.jar的jar包了。赶紧打开看一下:

国际化jar包
国际化jar包

我们在DefaultServlet源码里面看到这样一行代码 protected static final StringManager sm = StringManager.getManager("org.apache.catalina.servlets");传了一个包路径,刚好在tomcat-i18n-zh-CN.jar里面能找到一样的包路径,顺着这个包路径可以找到这样一个properties文件,如下截图:

注意包路径
注意包路径

把这个properties文件里面的复制出来,放在eclipse里面进行查看。然后在eclipse里面安装一个插件就可以查看properties文件里面的中文了。安装步骤如下:

打开eclipse的插件市场
打开eclipse的插件市场

搜索Properties Editor并进行Install(安装),

搜索Properties Editor
搜索Properties Editor
安装Properties Editor
安装Properties Editor
eclipse设置Properties文件
eclipse设置Properties文件
eclipse设置Properties文件
eclipse设置Properties文件
可以看到中文了
可以看到中文了

到这里就明白了,

response.sendError(404, sm.getString("defaultServlet.missingResource", new Object[] { requestUri }));

这里的sm.getString("defaultServlet.missingResource", new Object[] { requestUri })实际上就是 "请求的资源[{0}]不可用",里面的{0}被替换成requestUri了。

请求的资源不可用
请求的资源不可用

4 不能光看源码,要找证据

看到这里,我们只找到了404页面上的一部分中文数据,那剩下的中文都是在哪里传过去的呢?还有404的html代码DefaultServlet是在哪里返回的?还有这个404到底是不是在serveResource方法返回的,我们也不确定。因为我们只是看源码看到了有404这个错误,但实际上代码到底走不走这里我们都不确定。 如果你往下看DefaultServlet的源码,你会发现renderHtml这个方法。但是renderHtml这个方法跟404的html页面没有任何关系。

renderHtml方法
renderHtml方法

刚好,之前我看过阿里巴巴公司开源的神器Arthas,利用这个工具可以追踪一下当发生404的时候DefaultServlet类有没有被调用?如果调用了,调用了DefaultServlet类的哪些方法?

4.2 Arthas的trace命令

启动Arthas并监控tomcat的JVM进程,然后执行这个命令: trace org.apache.catalina.servlets.DefaultServlet *

trace命令
trace命令

使用这个命令可以监控DefaultServlet这个类的所有方法,并且打印方法的调用路径。看下Arthas的官方文档怎么说。

Arthas官方文档
Arthas官方文档

Arthas官方文档 https://arthas.aliyun.com/doc/trace.html

先执行trace命令,然后在浏览器上面故意访问一个不存在的地址,让它报404错误。

DefaultServlet类的方法调用路径
DefaultServlet类的方法调用路径

从上面的截图中,我们可以看到DefaultServlet类的service方法被调用了,然后service方法又调用了doGet方法,又调用了serveResource方法,最终在serveResource方法的第852行执行了 response.sendError(404, sm.getString("defaultServlet.missingResource", new Object[] { requestUri })); 这行代码,然后serveResource方法碰见return就结束了。

serveResource方法的第852行
serveResource方法的第852行

现在可以确定当发生404错误的时候,是由DefaultServlet类中的serveResource方法的第852行通过HttpServletResponse的sendError方法返回出去的。但是404的html代码是在哪返回出去的还是没找到。我们上面看过HttpServletResponse的源码,知道HttpServletResponse是一个抽象接口类,那404的html源码是不是在HttpServletResponse的实现类中返回的呢?我们需要想办法找到调用DefaultServlet类的service方法的地方,看看它调用DefaultServlet类的service方法传的HttpServletResponse实现类到底是什么?

4.3 Arthas的stack命令

Arthas的stack命令可以输出当前方法被调用的调用路径,我们可以使用这个命令看看当发生404错误的时候,是谁在调用DefaultServlet类的service方法? 执行这个命令: stack org.apache.catalina.servlets.DefaultServlet service

Arthas的stack命令
Arthas的stack命令

通过上图可以看到DefaultServlet类的service方法是从org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:452)这里,一路调过来的。我们看下CoyoteAdapter.service的源码

CoyoteAdapter.service方法的源码
CoyoteAdapter.service方法的源码

可以看到HttpServletResponse的真正实现类是org.apache.catalina.connector.Response,看下Response的源码

Response的源码
Response的源码

在Response的源码里面没有发现什么重要线索,我们继续顺着stack org.apache.catalina.servlets.DefaultServlet service这个命令的结果一步一步找下去,找到最后你会发现你也找不到404的html代码到底是在哪返回的。不过stack这个命令功劳也不小,帮我们发现了是谁在调用DefaultServlet类的service方法。那接下来怎么办?怎么才能找到404的html代码到底是在哪里返回的呢?

4.2 看Tomcat真正的源码

要知道Tomcat是开源的,我们干脆去github上面把Tomcat的真正源码下载下来,直接在Tomcat的java文件里面搜索一下看看。

Tomcat的源码
Tomcat的源码

然后使用git把Tomcat的源码下载下来

Tomcat的源码
Tomcat的源码

使用git命令进行下载

git clone git@github.com:apache/tomcat.git
git clone git@github.com:apache/tomcat.git

下载完成使用文本编辑Sublime Text打开tomcat的源码。然后我们需要想想怎么搜,搜什么?我们在看一遍404页面上面都有什么?

404页面
404页面

看完之后我觉得搜HTTP状态 404 - 未找到这句话最合适。但是这句话肯定不是写死在代码里面的,肯定是写在properties这个国际化文件里面的,我们去tomcat-i18n-zh-CN.jar这个国际化资源jar包里面找一找。而且这句话里面的404肯定也不是写死的,因此我们只搜HTTP状态这个几个字。

搜索HTTP状态
搜索HTTP状态
搜索结果
搜索结果

根据搜索结果,我们知道HTTP状态这个几个字在下面这个文件里面:

properties国际化文件里面
properties国际化文件里面

根据上面的搜索结果,我们再搜errorReportValve.statusHeader这个东西,Tomcat的代码里面肯定会引用errorReportValve.statusHeader这个东西。

搜errorReportValve.statusHeader
搜errorReportValve.statusHeader

根据搜索结果我们发现了一个比较熟悉的类就是org.apache.catalina.valves.ErrorReportValve.java。这个类我们在stack org.apache.catalina.servlets.DefaultServlet service这个命令的结果里面看到过。这个类在调用DefaultServlet类的service方法的调用路径上面有。赶紧去看看ErrorReportValve.java的源码。

ErrorReportValve.java的源码
ErrorReportValve.java的源码

在这里我们发现了很多线索,发现了下面这些代码

reason = smClient.getString("http." + statusCode + ".reason");

description = smClient.getString("http." + statusCode + ".desc");

smClient.getString("errorReportValve.statusHeader",

然后再跟我们之前用Fiddler工具抓包,抓到的404html代码对比以下发现是一致的。那404的html代码到底是不是这些代码返回的呢?我们在看一下stack org.apache.catalina.servlets.DefaultServlet service这个命令的结果截图,就可以肯定是这些代码返回的了。

ErrorReportValve.java的源码
ErrorReportValve.java的源码
ErrorReportValve.java的源码
ErrorReportValve.java的源码
ErrorReportValve.java的源码,铁证如山
ErrorReportValve.java的源码,铁证如山
ErrorReportValve.java的源码,铁证如山
ErrorReportValve.java的源码,铁证如山

到这里就结束了,我们终于知道404这个页面是怎么返回出去的了。阿里巴巴的神器Arthas帮了我们大忙,要不是Arthas光看源码估计很难找到这些代码。

5 再说一个Servlet的小知识

我们平时开发Servlet接口时,步骤都是一样的。先继承javax.servlet.http.HttpServlet这个抽象类,然后重写doGet和doPost方法。很少有人会去重写service这个方法。那么service这个方法有什么用呢?实际上,当一个请求过来的时候,Tomcat把请求交给对应的Servlet去处理的时候Tomcat首先会调用Servlet类的service这个方法,service这个方法再根据请求的method类型决定到底是调用Servlet类的doGet还是doPost方法。 我们可以看一下javax.servlet.http.HttpServlet这个抽象类的service方法是怎么实现的,看下图:

HttpServlet类里面的service方法
HttpServlet类里面的service方法

看懂了没?当然我们也可以在自己的Servlet类里面重写这个service方法,此时当一个请求被我们的servlet处理的时候,我们自己的Servlet类的service方法就会被Tomcat通过反射被调用,你可以直接在service方法里面写相关的业务代码。也可以在service方法里面根据条件去决定调用doGet或者doPost方法。

大家注意一下自己平时写的servlet接口,你们重写doGet和doPost方法,一般都是这样写的:

1.在doGet方法里面调用doPost方法,然后在doPost里面写项目相关的业务代码。

2.在doPost方法里面调用doGet方法,然后在doGet方法里面写项目相关的业务代码。

但是你知道你为什么这样写吗?因为这样写,可以保证无论调用方调用我们的Servlet接口的时候用的是什么类型的method来调用,你这个servlet都可以处理。实际上这样写是不规范的,规范的是我们在开发servlet接口的时候就规定这个servlet只能用get或者post的方式来调用,不能随意调用。

还有你知道你为啥很自觉的就重写doGet或者doPost方法吗?因为你的老师就是这么教你的,因为你不重写doGet或者doPost方法,并且也不重写service方法的时候,你的servlet会报错。为什么会报错,看javax.servlet.http.HttpServlet的源码,如下截图:

HttpServlet的源码
HttpServlet的源码

在HttpServlet的源码里面,doGet和doPost方法默认直接就报400错误了。

6 覆盖DefaultServlet

假如我们在我们自己的JavaWeb项目的web.xml里面定义了一个Servlet并且将这个Servlet的url-pattern配置为/,就相当于我们自己的servlet覆盖了tomcat的DefaultServlet。静态资源文件还有404等等,所有意外的错误请求都得由我们自己处理了。

6.1 看SpringMVC是怎么覆盖DefaultServlet的

SpringMVC的官方文档 https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-servlet

SpringMVC的官方文档
SpringMVC的官方文档

Spring MVC allows for mapping the DispatcherServlet to / (thus overriding the mapping of the container’s default Servlet), while still allowing static resource requests to be handled by the container’s default Servlet. It configures a DefaultServletHttpRequestHandler with a URL mapping of /** and the lowest priority relative to other URL mappings.

这段英文的意思是,SpringMVC允许DispatcherServlet的url mapping映射配置为/(这将覆盖web容器里面的默认servlet),然而SpringMVC仍然允许由WEB容器里面默认的servlet处理静态资源。通过将DefaultServletHttpRequestHandler的URL mapping配置为/** 这个配置相对于其他的URL mapping优先级是比较低的。

This handler forwards all requests to the default Servlet. Therefore, it must remain last in the order of all other URL HandlerMappings. That is the case if you use mvc:annotation-driven. Alternatively, if you set up your own customized HandlerMapping instance, be sure to set its order property to a value lower than that of the DefaultServletHttpRequestHandler, which is Integer.MAX_VALUE.

这段英文的意思是:这个处理器(DefaultServletHttpRequestHandler)会将所有的请求转发给默认的servlet。在这之前,请确保它必须在所有的URL 请求处理中排在最后。使用mvc:annotation-driven这种方式,就是这种情况。如果你自定义了HandlerMapping的实例,请确保你自己的HandlerMapping的实例优先级在DefaultServletHttpRequestHandler之前,即你自定义的HandlerMapping实例的优先级的order值应该小于Integer.MAX_VALUE的值。

注意,在SpringMVC中如果你将DispatcherServlet的url mapping映射配置为/的话,DispatcherServlet就会覆盖tomcat容器中的DefaultServlet,然后SpringMVC提供了一个DefaultServletHttpRequestHandler类,这个DefaultServletHttpRequestHandler类的URL mapping是/**,然后DefaultServletHttpRequestHandler类会将静态资源转发给tomcat的DefaultServlet去处理,DefaultServletHttpRequestHandler类自己并不处理静态资源。这就是上面SpringMVC官方文档中说的:“然而SpringMVC仍然允许由WEB容器里面默认的servlet处理静态资源。”

当SpringMVC的DispatcherServlet的url mapping映射配置为/的时候,SpringMVC的DispatcherServlet就会代替Tomcat的DefaultServlet处理所有的请求。当SpringMVC的DispatcherServlet发现请求是静态资源(css,html,图片)或者这个请求在SpringMVC的容器中没有匹配的HandlerMapping的时候(404),SpringMVC的DispatcherServlet会把这个请求交给SpringMVC的DefaultServletHttpRequestHandler类去处理。DefaultServletHttpRequestHandler类当然也不会处理静态资源(css,html,图片)和404错误,DefaultServletHttpRequestHandler类会寻找当前Web容器中的默认Servlet,找到之后DefaultServletHttpRequestHandler类就会把静态资源(css,html,图片)和404错误交给Web容器的Servlet去处理。

我们还是借助Arthas看下当发生404的时候SpringMVC是怎么处理的?下面的截图中的程序,我用的tomcat6。我们看了上面SpringMVC的官方文档了解到静态资源和404错误依然是用Tomcat的DefaultServlet处理的。所以,我们依然使用Arthas的trace命令来监控DefaultServlet类里面的方法有没有被调用,截图如下: 先启动项目,然后故意访问一个不存在的页面,这个请求不能是jsp。 Arthas命令: trace org.apache.catalina.servlets.DefaultServlet *

tomcat6的DefaultServlet
tomcat6的DefaultServlet

从上图可以看到,当SpringMVC的DispatcherServlet的url mapping映射配置为/的时候,发生404时Tomcat的DefaultServlet里面的doGet方法依然被调用了。接下来,我们再看一下DefaultServlet里面的doGet方法是被谁调用的。 stack org.apache.catalina.servlets.DefaultServlet doGet

tomcat6的DefaultServlet
tomcat6的DefaultServlet

从上图可以看到(从下往上看),发生404的时候,SpringMVC的调用栈如下: at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:844) 接下来 at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:859) 接下来 at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:968) 接下来 at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:893) 接下来 at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:959 接下来 at org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter.handle(HttpRequestHandlerAdapter.java:51) 接下来 at org.springframework.web.servlet.resource.DefaultServletHttpRequestHandler.handleRequest(DefaultServletHttpRequestHandler.java:122) 终于看到DefaultServletHttpRequestHandler这个类了,我们来看下DefaultServletHttpRequestHandler的源码,如下截图:

spring-webmvc-4.2.5.RELEASE.jar的DefaultServletHttpRequestHandler类源码
spring-webmvc-4.2.5.RELEASE.jar的DefaultServletHttpRequestHandler类源码

在DefaultServletHttpRequestHandler源码这里可以看到,SpringMVC将请求转发给Web容器的默认Servlet了。

所以说,即使SpringMVC的DispatcherServlet的url mapping映射配置为/时,SpringMVC也是不处理静态资源和404错误的。静态资源和404错误依然是有Web容器的默认Servlet去处理的。

spring-webmvc-4.2.5.RELEASE.jar的DefaultServletHttpRequestHandler类源码中各个Web容器默认的Servlet名字
spring-webmvc-4.2.5.RELEASE.jar的DefaultServletHttpRequestHandler类源码中各个Web容器默认的Servlet名字

6.2 将Web容器中默认的Servlet名字修改掉

The caveat to overriding the / Servlet mapping is that the RequestDispatcher for the default Servlet must be retrieved by name rather than by path. The DefaultServletHttpRequestHandler tries to auto-detect the default Servlet for the container at startup time, using a list of known names for most of the major Servlet containers (including Tomcat, Jetty, GlassFish, JBoss, Resin, WebLogic, and WebSphere). If the default Servlet has been custom-configured with a different name, or if a different Servlet container is being used where the default Servlet name is unknown, then you must explicitly provide the default Servlet’s name, as the following example shows:

这段英文的意思是:SpringMVC获取Web容器中的默认Servlet的时候,是依赖Web容器中默认的名字去找的,不是依赖类路径去找的。DefaultServletHttpRequestHandler会尝试按照默认的名字去Web容器中找,主流的Web容器有这几个(Tomcat, Jetty, GlassFish, JBoss, Resin, WebLogic, and WebSphere)。如果你修改了Web容器中默认的Servlet名字,你必须主动告诉SpringMVC,像下面这样。

告诉SpringMVC容器中默认的Servlet修改后的名字
告诉SpringMVC容器中默认的Servlet修改后的名字

我们还是来看一下DefaultServletHttpRequestHandler的源码,如下截图:

spring-webmvc-4.2.5.RELEASE.jar的DefaultServletHttpRequestHandler类源码中各个Web容器默认的Servlet名字
spring-webmvc-4.2.5.RELEASE.jar的DefaultServletHttpRequestHandler类源码中各个Web容器默认的Servlet名字

我们来试一下,我们把Web容器中默认的Servlet名字改掉,然后不告诉SpringMVC的话,会发生什么情况。 首先去Tomcat的安装目录conf下面打开web.xml将默认的Servlet名字改掉。

把Web容器中默认的Servlet名字改掉,然后不告诉SpringMVC
把Web容器中默认的Servlet名字改掉,然后不告诉SpringMVC
把Web容器中默认的Servlet名字改掉,然后不告诉SpringMVC
把Web容器中默认的Servlet名字改掉,然后不告诉SpringMVC

然后启动项目,访问一个静态资源或者不存在的资源,看一下:

SpringMVC报错了
SpringMVC报错了

可以看到SpringMVC报错了。

SpringMVC报错了02
SpringMVC报错了02

再来看下一DefaultServletHttpRequestHandler的源码,如下截图:

DefaultServletHttpRequestHandler报错的源码
DefaultServletHttpRequestHandler报错的源码

我们将修改后的名字告诉SpringMVC,再来看一下。

将修改后的名字告诉SpringMVC
将修改后的名字告诉SpringMVC

<mvc:default-servlet-handler default-servlet-name="default123"/>

然后启动项目,访问一个静态资源或者不存在的资源,看一下:

SpringMVC不报错了
SpringMVC不报错了

可以看到SpringMVC不报错了,成功的将请求交给Web容器中默认的Servlet处理了。并且成功返回了404错误页面。

6.3 Weblogic容器

从DefaultServletHttpRequestHandler的源码里面我们知道了Weblogic容器里面默认的Servlet名字叫FileServlet。但是知道这个没用,由于Weblogic是Oracle公司的收费软件,不是开源软件。所以不像Tomcat一样这么自由,什么东西都可以自由修改和自由配置。Weblogic容器默认的Servlet名字,我到现在都不知道怎么改。类似Tomcat的conf里面的全局配置web.xml文件我都没找到。不过FileServlet这个类在weblogic.jar这个jar包里面。weblogic.jar这个jar包在 weblogic的安装路径下面:/weblogic的安装路径/weblogic/wls/wlserver_10.3/server/lib。谁知道怎么修改Weblogic容器里面默认的Servlet名字,可以评论分享一下,非常感谢。

6.3 Weblogic的404页面

Weblogic的404页面
Weblogic的404页面

用Filddler抓包抓到的内容如下:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Draft//EN">
<HTML>
 <HEAD>
  <TITLE>Error 404--Not Found</TITLE>
 </HEAD>
 <BODY bgcolor="white">
  <FONT FACE=Helvetica><BR CLEAR=all>
  <TABLE border=0 cellspacing=5><TR><TD><BR CLEAR=all>
  <FONT FACE="Helvetica" COLOR="black" SIZE="3"><H2>Error 404--Not Found</H2>
  </FONT></TD></TR>
  </TABLE>
  <TABLE border=0 width=100% cellpadding=10><TR><TD VALIGN=top WIDTH=100% BGCOLOR=white><FONT FACE="Courier New"><FONT FACE="Helvetica" SIZE="3"><H3>From RFC 2068
    <i>Hypertext Transfer Protocol -- HTTP/1.1</i>:</H3>
  </FONT><FONT FACE="Helvetica" SIZE="3"><H4>10.4.5 404 Not Found</H4>
  </FONT><P><FONT FACE="Courier New">The server has not found anything matching the Request-URI. No indication is given of whether the condition is temporary or permanent.</p>
   <p>If the server does not wish to make this information available to the client, the status code 403 (Forbidden) can be used instead. The 410 (Gone) status code SHOULD be used if the server knows, through some internally configurable mechanism, that an old resource is permanently unavailable and has no forwarding address.</FONT></P>
  </FONT></TD></TR>
  </TABLE>
 
 </BODY>
</HTML>

用Arthas的trace命令追踪一下: trace weblogic.servlet.FileServlet *

weblogic.servlet.FileServlet的源码
weblogic.servlet.FileServlet的源码

FileServlet也是继承了javax.servlet.http.HttpServlet类,都是一样的东西。

然后启动一个Weblogic的Web项目,然后故意访问一个不存在的页面,让Weblogic返回一个404页面,然后就可以看到trace命令的监控结果了,如下截图:

trace weblogic.servlet.FileServlet * 监控结果
trace weblogic.servlet.FileServlet * 监控结果

可以看到FileServlet的service方法被调用了,然后代码最终findSource方法的第269行结束了。 也就是这行paramHttpServletResponse.sendError(404);

trace 命令结果分析
trace 命令结果分析
FileServlet源码
FileServlet源码

从源码可以看出404还是利用javax.servlet.http.HttpServletResponse这个类返回出的,但是javax.servlet.http.HttpServletResponse是个抽象类,还是得找到具体的实现类。继续使用Arthas的stack命令: stack weblogic.servlet.FileServlet service

stack 监控命令结果
stack 监控命令结果

从stack监控命令结果上面来看,我觉得有必要看一下weblogic.servlet.internal.WebAppServletContext.execute(WebAppServletContext.java:2182)这行代码。因为WebAppServletContext这个类的名字的意思就是“Web应用Servlet上下文”,所以我怀疑抽象类javax.servlet.http.HttpServletResponse的实现类应该就是从WebAppServletContext类里面一路传过去的,看源码吧,如下截图:

错误的WebAppServletContext.java
错误的WebAppServletContext.java

我找啊找,看啊看,又打算看一下weblogic.security.acl.internal.AuthenticatedSubject.doAs(AuthenticatedSubject.java:321)这个类,结果如下截图:

找错jar包了
找错jar包了

从AuthenticatedSubject和AuthenticatedSubject这俩个类来看,我可以肯定我是找错jar包了。WebAppServletContext.java这个类并不在weblogic.jar包里面。那这俩个类到底在哪个jar包里面呢? Arthas这个神器又可以帮上忙了,Arthas里面有一个命令classloader可以找到某个类是从哪个jar包加载进来的,我们先使用classloader -l命令看一下JVM里面都有哪些类加载器,如下截图:

classloader -l 命令结果
classloader -l 命令结果

然后,从classloader -l命令结果里面随便找一个跟weblogic有关的类加载器,找他他的hash值,然后使用这个命令:classloader -c 7729472e -r weblogic/servlet/internal/WebAppServletContext.class 就可以找到我们指定的类是从哪个jar包里面找到的了,如下截图:

classloader 找到了俩个jar包
classloader 找到了俩个jar包
SecurityManager.java类
SecurityManager.java类

注意,命令找到了俩个jar包,看到命令结果我一下就明白了。因为我们的weblogic在前些日子打过一次补丁,这个BUG29633432_10360190716.jar就是当时打补丁搞上去的jar包。赶紧把BUG29633432_10360190716.jar这个jar包下载下来反编译一下,如下截图:

终于找到WebAppServletContext.execute(WebAppServletContext.java:2182)
终于找到WebAppServletContext.execute(WebAppServletContext.java:2182)

终于找到weblogic.servlet.internal.WebAppServletContext.execute(WebAppServletContext.java:2182)这行代码了,然后抽象类javax.servlet.http.HttpServletResponse的具体实现类也找到了weblogic.servlet.internal.ServletResponseImpl.java,既然找到HttpServletResponse的具体实现类了,就赶紧看一下paramHttpServletResponse.sendError(404);这个方法吧,如下截图:

sendError(404)的具体实现方法
sendError(404)的具体实现方法

我们先来看 ErrorMessages.getErrorPage(code)这个方法吧:

ErrorMessages.java类里面关于404的描述
ErrorMessages.java类里面关于404的描述
ErrorMessages.java类里面关于404的描述
ErrorMessages.java类里面关于404的描述
404页面就是在这里组装出来的
404页面就是在这里组装出来的
再来看一遍weblogic返回的404页面
再来看一遍weblogic返回的404页面
好了到这里就结束了
好了到这里就结束了

好了,到这里就结束了。Tomcat和Weblogic的404页面我们都在源码里面找到了。

四千岁

2021/12/11  阅读:47  主题:默认主题

作者介绍

四千岁